Consul is a distributed, highly available, and data center aware solution to connect and configure applications across dynamic, distributed infrastructure.
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.
 
 
 
 
 
 

184 lines
4.0 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package sprawl
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"text/tabwriter"
"time"
retry "github.com/avast/retry-go"
"github.com/hashicorp/consul/api"
)
// PrintDetails will dump relevant addressing and naming data to the logger for
// human interaction purposes.
func (s *Sprawl) PrintDetails() error {
det := logDetails{
TopologyID: s.topology.ID,
}
for _, cluster := range s.topology.Clusters {
client := s.clients[cluster.Name]
var cfg *api.RaftConfiguration
var err error
err = retry.Do(
func() error {
cfg, err = client.Operator().RaftGetConfiguration(nil)
if err != nil {
return fmt.Errorf("error get raft config: %w", err)
}
return nil
},
retry.MaxDelay(5*time.Second),
retry.Attempts(15),
)
if err != nil {
return fmt.Errorf("could not get raft config for cluster %q: %w", cluster.Name, err)
}
var leaderNode string
for _, svr := range cfg.Servers {
if svr.Leader {
leaderNode = strings.TrimSuffix(svr.Node, "-pod")
}
}
cd := clusterDetails{
Name: cluster.Name,
Leader: leaderNode,
}
for _, node := range cluster.Nodes {
if node.Disabled {
continue
}
var addrs []string
for _, addr := range node.Addresses {
addrs = append(addrs, addr.Network+"="+addr.IPAddress)
}
sort.Strings(addrs)
if node.IsServer() {
cd.Apps = append(cd.Apps, appDetail{
Type: "server",
Container: node.DockerName(),
Addresses: addrs,
ExposedPort: node.ExposedPort(8500),
})
}
for _, wrk := range node.Workloads {
if wrk.IsMeshGateway {
cd.Apps = append(cd.Apps, appDetail{
Type: "mesh-gateway",
Container: node.DockerName(),
ExposedPort: node.ExposedPort(wrk.Port),
ExposedEnvoyAdminPort: node.ExposedPort(wrk.EnvoyAdminPort),
Addresses: addrs,
Service: wrk.ID.String(),
})
} else {
cd.Apps = append(cd.Apps, appDetail{
Type: "app",
Container: node.DockerName(),
ExposedPort: node.ExposedPort(wrk.Port),
ExposedEnvoyAdminPort: node.ExposedPort(wrk.EnvoyAdminPort),
Addresses: addrs,
Service: wrk.ID.String(),
})
}
}
}
det.Clusters = append(det.Clusters, cd)
}
var buf bytes.Buffer
w := tabwriter.NewWriter(&buf, 0, 0, 3, ' ', tabwriter.Debug)
score := map[string]int{
"server": 0,
"mesh-gateway": 1,
"app": 2,
}
for _, cluster := range det.Clusters {
fmt.Fprintf(w, "CLUSTER\tTYPE\tCONTAINER\tNAME\tADDRS\tPORTS\t\n")
sort.Slice(cluster.Apps, func(i, j int) bool {
a := cluster.Apps[i]
b := cluster.Apps[j]
asc := score[a.Type]
bsc := score[b.Type]
if asc < bsc {
return true
} else if asc > bsc {
return false
}
if a.Container < b.Container {
return true
} else if a.Container > b.Container {
return false
}
return a.Service < b.Service
})
for _, d := range cluster.Apps {
if d.Type == "server" && d.Container == cluster.Leader {
d.Type = "leader"
}
portStr := "app=" + strconv.Itoa(d.ExposedPort)
if d.ExposedEnvoyAdminPort > 0 {
portStr += " envoy=" + strconv.Itoa(d.ExposedEnvoyAdminPort)
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t\n",
cluster.Name,
d.Type,
d.Container,
d.Service,
strings.Join(d.Addresses, ", "),
portStr,
)
}
fmt.Fprintf(w, "\t\t\t\t\t\n")
}
w.Flush()
s.logger.Debug("CURRENT SPRAWL DETAILS", "details", buf.String())
return nil
}
type logDetails struct {
TopologyID string
Clusters []clusterDetails
}
type clusterDetails struct {
Name string
Leader string
Apps []appDetail
}
type appDetail struct {
Type string // server|mesh-gateway|app
Container string
Addresses []string
ExposedPort int `json:",omitempty"`
ExposedEnvoyAdminPort int `json:",omitempty"`
// just services
Service string `json:",omitempty"`
}