diff --git a/cmd/headscale/cli/namespaces.go b/cmd/headscale/cli/namespaces.go index 8240187d..9f4a66ee 100644 --- a/cmd/headscale/cli/namespaces.go +++ b/cmd/headscale/cli/namespaces.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "log" + "strings" "github.com/spf13/cobra" ) @@ -22,16 +23,21 @@ var CreateNamespaceCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { + o, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { log.Fatalf("Error initializing: %s", err) } - _, err = h.CreateNamespace(args[0]) - if err != nil { - fmt.Println(err) + namespace, err := h.CreateNamespace(args[0]) + if strings.HasPrefix(o, "json") { + jsonOutput(namespace, err, o) return } - fmt.Printf("Ook.\n") + if err != nil { + fmt.Printf("Error creating namespace: %s\n", err) + return + } + fmt.Printf("Namespace created\n") }, } @@ -39,17 +45,22 @@ var ListNamespacesCmd = &cobra.Command{ Use: "list", Short: "List all the namespaces", Run: func(cmd *cobra.Command, args []string) { + o, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { log.Fatalf("Error initializing: %s", err) } - ns, err := h.ListNamespaces() + namespaces, err := h.ListNamespaces() + if strings.HasPrefix(o, "json") { + jsonOutput(namespaces, err, o) + return + } if err != nil { fmt.Println(err) return } fmt.Printf("ID\tName\n") - for _, n := range *ns { + for _, n := range *namespaces { fmt.Printf("%d\t%s\n", n.ID, n.Name) } }, diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 08b7e4d8..b449c6da 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "log" + "strings" "github.com/spf13/cobra" ) @@ -21,17 +22,22 @@ var RegisterCmd = &cobra.Command{ if err != nil { log.Fatalf("Error getting namespace: %s", err) } + o, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { log.Fatalf("Error initializing: %s", err) } - err = h.RegisterMachine(args[0], n) - if err != nil { - fmt.Printf("Error: %s", err) + m, err := h.RegisterMachine(args[0], n) + if strings.HasPrefix(o, "json") { + jsonOutput(m, err, o) return } - fmt.Println("Ook.") + if err != nil { + fmt.Printf("Cannot register machine: %s\n", err) + return + } + fmt.Printf("Machine registered\n") }, } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 8b2b274f..0738e18b 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -1,6 +1,8 @@ package cli import ( + "encoding/json" + "fmt" "io" "log" "os" @@ -13,6 +15,10 @@ import ( "tailscale.com/tailcfg" ) +type ErrorOutput struct { + Error string +} + func absPath(path string) string { // If a relative path is provided, prefix it with the the directory where // the config file was found. @@ -72,3 +78,35 @@ func loadDerpMap(path string) (*tailcfg.DERPMap, error) { err = yaml.Unmarshal(b, &derpMap) return &derpMap, err } + +func jsonOutput(result interface{}, errResult error, outputFormat string) { + var j []byte + var err error + switch outputFormat { + case "json": + if errResult != nil { + j, err = json.MarshalIndent(ErrorOutput{errResult.Error()}, "", "\t") + if err != nil { + log.Fatalln(err) + } + } else { + j, err = json.MarshalIndent(result, "", "\t") + if err != nil { + log.Fatalln(err) + } + } + case "json-line": + if errResult != nil { + j, err = json.Marshal(ErrorOutput{errResult.Error()}) + if err != nil { + log.Fatalln(err) + } + } else { + j, err = json.Marshal(result) + if err != nil { + log.Fatalln(err) + } + } + } + fmt.Println(string(j)) +} diff --git a/cmd/headscale/headscale.go b/cmd/headscale/headscale.go index 476a5431..974227ea 100644 --- a/cmd/headscale/headscale.go +++ b/cmd/headscale/headscale.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "log" @@ -19,7 +20,27 @@ var versionCmd = &cobra.Command{ Short: "Print the version.", Long: "The version of headscale.", Run: func(cmd *cobra.Command, args []string) { - fmt.Println(version) + + o, _ := cmd.Flags().GetString("output") + switch o { + case "": + fmt.Println(version) + case "json": + j, err := json.MarshalIndent(map[string]string{"version": version}, "", "\t") + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(j)) + + case "json-line": + j, err := json.Marshal(map[string]string{"version": version}) + if err != nil { + log.Fatalln(err) + } + fmt.Println(string(j)) + default: + fmt.Printf("Unknown format %s\n", o) + } }, } @@ -123,6 +144,8 @@ func main() { cli.CreatePreAuthKeyCmd.PersistentFlags().Bool("reusable", false, "Make the preauthkey reusable") cli.CreatePreAuthKeyCmd.Flags().StringP("expiration", "e", "", "Human-readable expiration of the key (30m, 24h, 365d...)") + headscaleCmd.PersistentFlags().StringP("output", "o", "", "Output format. Empty for human-readable, 'json' or 'json-line'") + if err := headscaleCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1)