You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/command/operator/autopilot/state/formatter.go

210 lines
6.3 KiB

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
1 year ago
// SPDX-License-Identifier: BUSL-1.1
package state
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/hashicorp/consul/api"
)
const (
PrettyFormat string = "pretty"
JSONFormat string = "json"
)
// Formatter defines methods provided by an autopilot state output formatter
type Formatter interface {
FormatState(state *api.AutopilotState) (string, error)
}
// GetSupportedFormats returns supported formats
func GetSupportedFormats() []string {
return []string{PrettyFormat, JSONFormat}
}
// NewFormatter returns Formatter implementation
func NewFormatter(format string) (formatter Formatter, err error) {
switch format {
case PrettyFormat:
formatter = newPrettyFormatter()
case JSONFormat:
formatter = newJSONFormatter()
default:
err = fmt.Errorf("Unknown format: %s", format)
}
return formatter, err
}
func newPrettyFormatter() Formatter {
return &prettyFormatter{}
}
type prettyFormatter struct {
}
func outputStringSlice(buffer *bytes.Buffer, indent string, values []string) {
for _, val := range values {
buffer.WriteString(fmt.Sprintf("%s%s\n", indent, val))
}
}
type mapOutput struct {
key string
value string
}
func formatZone(zoneName string, zone *api.AutopilotZone) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s:\n", zoneName))
buffer.WriteString(fmt.Sprintf(" Failure Tolerance: %d\n", zone.FailureTolerance))
buffer.WriteString(" Voters:\n")
outputStringSlice(&buffer, " ", zone.Voters)
buffer.WriteString(" Servers:\n")
outputStringSlice(&buffer, " ", zone.Servers)
return buffer.String()
}
func formatServer(srv *api.AutopilotServer) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s\n", srv.ID))
buffer.WriteString(fmt.Sprintf(" Name: %s\n", srv.Name))
buffer.WriteString(fmt.Sprintf(" Address: %s\n", srv.Address))
buffer.WriteString(fmt.Sprintf(" Version: %s\n", srv.Version))
buffer.WriteString(fmt.Sprintf(" Status: %s\n", srv.Status))
buffer.WriteString(fmt.Sprintf(" Node Type: %s\n", srv.NodeType))
buffer.WriteString(fmt.Sprintf(" Node Status: %s\n", srv.NodeStatus))
buffer.WriteString(fmt.Sprintf(" Healthy: %t\n", srv.Healthy))
buffer.WriteString(fmt.Sprintf(" Last Contact: %s\n", srv.LastContact.String()))
buffer.WriteString(fmt.Sprintf(" Last Term: %d\n", srv.LastTerm))
buffer.WriteString(fmt.Sprintf(" Last Index: %d\n", srv.LastIndex))
if srv.RedundancyZone != "" {
buffer.WriteString(fmt.Sprintf(" Redundancy Zone: %s\n", srv.RedundancyZone))
}
if srv.UpgradeVersion != "" {
buffer.WriteString(fmt.Sprintf(" Upgrade Version: %s\n", srv.UpgradeVersion))
}
if srv.ReadReplica {
buffer.WriteString(fmt.Sprintf(" Read Replica: %t\n", srv.ReadReplica))
}
if len(srv.Meta) > 0 {
buffer.WriteString(fmt.Sprintf(" Meta\n"))
var outputs []mapOutput
for k, v := range srv.Meta {
outputs = append(outputs, mapOutput{key: k, value: fmt.Sprintf(" %q: %q\n", k, v)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
return buffer.String()
}
func (f *prettyFormatter) FormatState(state *api.AutopilotState) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("Healthy: %t\n", state.Healthy))
buffer.WriteString(fmt.Sprintf("Failure Tolerance: %d\n", state.FailureTolerance))
buffer.WriteString(fmt.Sprintf("Optimistic Failure Tolerance: %d\n", state.OptimisticFailureTolerance))
buffer.WriteString(fmt.Sprintf("Leader: %s\n", state.Leader))
buffer.WriteString("Voters:\n")
outputStringSlice(&buffer, " ", state.Voters)
if len(state.ReadReplicas) > 0 {
buffer.WriteString("Read Replicas:\n")
outputStringSlice(&buffer, " ", state.ReadReplicas)
}
if len(state.RedundancyZones) > 0 {
var outputs []mapOutput
buffer.WriteString("Redundancy Zones:\n")
for zoneName, zone := range state.RedundancyZones {
outputs = append(outputs, mapOutput{key: zoneName, value: formatZone(zoneName, &zone)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
if state.Upgrade != nil {
u := state.Upgrade
buffer.WriteString("Upgrade:\n")
buffer.WriteString(fmt.Sprintf(" Status: %s\n", u.Status))
buffer.WriteString(fmt.Sprintf(" Target Version: %s\n", u.TargetVersion))
if len(u.TargetVersionVoters) > 0 {
buffer.WriteString(" Target Version Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionVoters)
}
if len(u.TargetVersionNonVoters) > 0 {
buffer.WriteString(" Target Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionNonVoters)
}
if len(u.TargetVersionReadReplicas) > 0 {
buffer.WriteString(" Target Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.TargetVersionReadReplicas)
}
if len(u.OtherVersionVoters) > 0 {
buffer.WriteString(" Other Version Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionVoters)
}
if len(u.OtherVersionNonVoters) > 0 {
buffer.WriteString(" Other Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionNonVoters)
}
if len(u.OtherVersionReadReplicas) > 0 {
buffer.WriteString(" Other Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.OtherVersionReadReplicas)
}
}
buffer.WriteString("Servers:\n")
var outputs []mapOutput
for id, srv := range state.Servers {
outputs = append(outputs, mapOutput{key: id, value: formatServer(&srv)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
return buffer.String(), nil
}
func newJSONFormatter() Formatter {
return &jsonFormatter{}
}
type jsonFormatter struct {
}
func (f *jsonFormatter) FormatState(state *api.AutopilotState) (string, error) {
b, err := json.MarshalIndent(state, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal token: %v", err)
}
return string(b), nil
}