cli: consolidate output and table rendering

This commit is contained in:
Kristoffer Dalby
2026-06-16 12:07:36 +00:00
parent 0b7c8aa270
commit b56326427c
5 changed files with 43 additions and 53 deletions
+3 -6
View File
@@ -7,7 +7,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
)
@@ -51,9 +50,7 @@ var listAPIKeys = &cobra.Command{
}
return printListOutput(cmd, response.GetApiKeys(), func() error {
tableData := make(pterm.TableData, 1, 1+len(response.GetApiKeys()))
tableData[0] = []string{"ID", "Prefix", colExpiration, colCreated}
rows := make([][]string, 0, len(response.GetApiKeys()))
for _, key := range response.GetApiKeys() {
expiration := "-"
@@ -61,7 +58,7 @@ var listAPIKeys = &cobra.Command{
expiration = ColourTime(key.GetExpiration().AsTime())
}
tableData = append(tableData, []string{
rows = append(rows, []string{
strconv.FormatUint(key.GetId(), util.Base10),
key.GetPrefix(),
expiration,
@@ -69,7 +66,7 @@ var listAPIKeys = &cobra.Command{
})
}
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
return renderTable([]string{"ID", "Prefix", colExpiration, colCreated}, rows)
})
}),
}
+12 -15
View File
@@ -8,7 +8,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
)
@@ -52,18 +51,7 @@ var listPreAuthKeys = &cobra.Command{
}
return printListOutput(cmd, response.GetPreAuthKeys(), func() error {
tableData := make(pterm.TableData, 1, 1+len(response.GetPreAuthKeys()))
tableData[0] = []string{
"ID",
"Key/Prefix",
"Reusable",
"Ephemeral",
"Used",
colExpiration,
colCreated,
"Owner",
}
rows := make([][]string, 0, len(response.GetPreAuthKeys()))
for _, key := range response.GetPreAuthKeys() {
expiration := "-"
if key.GetExpiration() != nil {
@@ -79,7 +67,7 @@ var listPreAuthKeys = &cobra.Command{
owner = "-"
}
tableData = append(tableData, []string{
rows = append(rows, []string{
strconv.FormatUint(key.GetId(), util.Base10),
key.GetKey(),
strconv.FormatBool(key.GetReusable()),
@@ -91,7 +79,16 @@ var listPreAuthKeys = &cobra.Command{
})
}
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
return renderTable([]string{
"ID",
"Key/Prefix",
"Reusable",
"Ephemeral",
"Used",
colExpiration,
colCreated,
"Owner",
}, rows)
})
}),
}
+1 -7
View File
@@ -120,13 +120,7 @@ func filterPreReleasesIfStable(versionFunc func() string) func(string) bool {
}
// If we are on a stable release, filter out pre-releases.
for _, ignore := range prereleases {
if strings.Contains(tag, ignore) {
return true
}
}
return false
return isPreReleaseVersion(tag)
}
}
+4 -7
View File
@@ -10,7 +10,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/pterm/pterm"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
@@ -194,12 +193,10 @@ var listUsersCmd = &cobra.Command{
}
return printListOutput(cmd, response.GetUsers(), func() error {
tableData := make(pterm.TableData, 1, 1+len(response.GetUsers()))
tableData[0] = []string{"ID", "Name", "Username", "Email", colCreated}
rows := make([][]string, 0, len(response.GetUsers()))
for _, user := range response.GetUsers() {
tableData = append(
tableData,
rows = append(
rows,
[]string{
strconv.FormatUint(user.GetId(), util.Base10),
user.GetDisplayName(),
@@ -210,7 +207,7 @@ var listUsersCmd = &cobra.Command{
)
}
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
return renderTable([]string{"ID", "Name", "Username", "Email", colCreated}, rows)
})
}),
}
+23 -18
View File
@@ -15,6 +15,7 @@ import (
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/prometheus/common/model"
"github.com/pterm/pterm"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"google.golang.org/grpc"
@@ -161,7 +162,8 @@ func newHeadscaleCLIWithConfig() (context.Context, v1.HeadscaleServiceClient, *g
return nil, nil, nil, nil, errAPIKeyNotSet
}
grpcOptions = append(grpcOptions,
grpcOptions = append(
grpcOptions,
grpc.WithPerRPCCredentials(tokenAuth{
token: apiKey,
}),
@@ -175,11 +177,13 @@ func newHeadscaleCLIWithConfig() (context.Context, v1.HeadscaleServiceClient, *g
InsecureSkipVerify: true,
}
grpcOptions = append(grpcOptions,
grpcOptions = append(
grpcOptions,
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
)
} else {
grpcOptions = append(grpcOptions,
grpcOptions = append(
grpcOptions,
grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(nil, "")),
)
}
@@ -268,9 +272,19 @@ func confirmAction(cmd *cobra.Command, prompt string) bool {
return util.YesNo(prompt)
}
// renderTable prints a human-readable pterm table with the given header row
// and data rows, using the shared header styling.
func renderTable(header []string, rows [][]string) error {
tableData := make(pterm.TableData, 0, 1+len(rows))
tableData = append(tableData, header)
tableData = append(tableData, rows...)
return pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
}
// printListOutput checks the --output flag: when a machine-readable format is
// requested it serialises data as JSON/YAML; otherwise it calls renderTable
// to produce the human-readable pterm table.
// requested it serialises data as JSON/YAML; otherwise it calls the render
// callback to produce the human-readable pterm table.
func printListOutput(
cmd *cobra.Command,
data any,
@@ -292,24 +306,15 @@ func printError(err error, outputFormat string) {
Error string `json:"error"`
}
e := errOutput{Error: err.Error()}
var formatted []byte
switch outputFormat {
case outputFormatJSON:
formatted, _ = json.MarshalIndent(e, "", "\t") //nolint:errchkjson // errOutput contains only a string field
case outputFormatJSONLine:
formatted, _ = json.Marshal(e) //nolint:errchkjson // errOutput contains only a string field
case outputFormatYAML:
formatted, _ = yaml.Marshal(e)
default:
if outputFormat == "" {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return
}
fmt.Fprintf(os.Stderr, "%s\n", formatted)
// formatOutput cannot fail here: errOutput is a single string field.
out, _ := formatOutput(errOutput{Error: err.Error()}, "", outputFormat)
fmt.Fprintf(os.Stderr, "%s\n", out)
}
func hasMachineOutputFlag() bool {