small pixel drawing of a pufferfish cascade

internal/cli/nodes.go

package cli

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"sort"
	"strings"
	"text/tabwriter"

	"j3s.sh/cascade/api"
)

type nodesCommand struct {
	flagAPIAddr string
}

func (c nodesCommand) Usage() {
	fmt.Printf(`usage: cascade nodes [flags]
    list the nodes in the cascade cluster.

flags:
  -api
    address of the cascade http api to target (default = 127.0.0.1:8500)
`)
}

func (c *nodesCommand) Init(args []string) {
	flags := flag.NewFlagSet("", flag.ContinueOnError)
	flags.Usage = c.Usage
	flags.StringVar(&c.flagAPIAddr, "api", "", "")
	if err := flags.Parse(args); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func RunNodes(args []string) {
	c := nodesCommand{}
	c.Init(args)

	cfg := api.DefaultConfig()
	if c.flagAPIAddr != "" {
		cfg.Address = c.flagAPIAddr
	}
	client, err := api.NewClient(cfg)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	nodes, err := client.Agent().Members()
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Fetch the full instance list once; group by owning node.
	instances, _ := client.Catalog().Instances()
	servicesByNode := map[string][]string{}
	for _, inst := range instances {
		servicesByNode[inst.Node] = append(servicesByNode[inst.Node], inst.ServiceName)
	}
	for n := range servicesByNode {
		sort.Strings(servicesByNode[n])
	}

	var b bytes.Buffer
	tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
	fmt.Fprintf(tw, "node\taddr\tstatus\tservices\n")
	for _, n := range nodes {
		services := "<none>"
		if names := servicesByNode[n.Name]; len(names) > 0 {
			services = strings.Join(names, ", ")
		}
		fmt.Fprintf(tw, "%s\t%s:%d\t%s\t%s\n", n.Name, n.Addr, n.Port, n.StatusPretty(), services)
	}
	if err := tw.Flush(); err != nil {
		fmt.Printf("error flushing tabwriter: %s", err)
		os.Exit(1)
	}
	fmt.Print(b.String())
}