From 33bee1df05c2d57ac47a9d6edad60b71dc21b37b Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 13:27:40 +0200 Subject: [PATCH 1/8] Do not print stuff in the library --- utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils.go b/utils.go index 27857455..55310c8d 100644 --- a/utils.go +++ b/utils.go @@ -105,8 +105,8 @@ func getRandomIP() (*net.IP, error) { ipo, ipnet, err := net.ParseCIDR("100.64.0.0/10") if err == nil { ip := ipo.To4() - fmt.Println("In Randomize IPAddr: IP ", ip, " IPNET: ", ipnet) - fmt.Println("Final address is ", ip) + // fmt.Println("In Randomize IPAddr: IP ", ip, " IPNET: ", ipnet) + // fmt.Println("Final address is ", ip) // fmt.Println("Broadcast address is ", ipb) // fmt.Println("Network address is ", ipn) r := mathrand.Uint32() @@ -119,7 +119,7 @@ func getRandomIP() (*net.IP, error) { ip[i] = ip[i] + (v &^ ipnet.Mask[i]) // fmt.Println("IP After: ", ip[i]) } - fmt.Println("FINAL IP: ", ip.String()) + // fmt.Println("FINAL IP: ", ip.String()) return &ip, nil } From 4b3b48441ff28af6fabbf6681038193263bd8fe8 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 13:27:53 +0200 Subject: [PATCH 2/8] Return the machine when registering --- cli.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/cli.go b/cli.go index f75bb470..9829ac36 100644 --- a/cli.go +++ b/cli.go @@ -1,50 +1,45 @@ package headscale import ( - "fmt" + "errors" "log" "tailscale.com/wgengine/wgcfg" ) // RegisterMachine is executed from the CLI to register a new Machine using its MachineKey -func (h *Headscale) RegisterMachine(key string, namespace string) error { +func (h *Headscale) RegisterMachine(key string, namespace string) (*Machine, error) { ns, err := h.GetNamespace(namespace) if err != nil { - return err + return nil, err } mKey, err := wgcfg.ParseHexKey(key) if err != nil { - log.Printf("Cannot parse client key: %s", err) - return err + return nil, err } db, err := h.db() if err != nil { log.Printf("Cannot open DB: %s", err) - return err + return nil, err } defer db.Close() m := Machine{} if db.First(&m, "machine_key = ?", mKey.HexString()).RecordNotFound() { - log.Printf("Cannot find machine with machine key: %s", mKey.Base64()) - return err + return nil, errors.New("Machine not found") } if m.isAlreadyRegistered() { - fmt.Println("This machine already registered") - return nil + return nil, errors.New("Machine already registered") } ip, err := h.getAvailableIP() if err != nil { - log.Println(err) - return err + return nil, err } m.IPAddress = ip.String() m.NamespaceID = ns.ID m.Registered = true m.RegisterMethod = "cli" db.Save(&m) - fmt.Println("Machine registered 🎉") - return nil + return &m, nil } From 3b34f715ce0925f5ac86cdc562022f65d1b73488 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 13:28:22 +0200 Subject: [PATCH 3/8] Adding support for JSON-formatted output 1/n --- cmd/headscale/cli/namespaces.go | 23 ++++++++++++++------ cmd/headscale/cli/nodes.go | 14 ++++++++---- cmd/headscale/cli/utils.go | 38 +++++++++++++++++++++++++++++++++ cmd/headscale/headscale.go | 25 +++++++++++++++++++++- 4 files changed, 89 insertions(+), 11 deletions(-) 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) From abde7dddb7485e93d6adc9007f30aebdfbec73ad Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 13:58:51 +0200 Subject: [PATCH 4/8] More json output --- cmd/headscale/cli/nodes.go | 6 ++++++ cmd/headscale/cli/preauthkeys.go | 18 +++++++++++++++--- cmd/headscale/cli/routes.go | 18 +++++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index b449c6da..36bc29d0 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -49,12 +49,18 @@ var ListNodesCmd = &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) } machines, err := h.ListMachinesInNamespace(n) + if strings.HasPrefix(o, "json") { + jsonOutput(machines, err, o) + return + } + if err != nil { log.Fatalf("Error getting nodes: %s", err) } diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go index ce189eba..ae6c4682 100644 --- a/cmd/headscale/cli/preauthkeys.go +++ b/cmd/headscale/cli/preauthkeys.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "log" + "strings" "time" "github.com/hako/durafmt" @@ -22,14 +23,20 @@ var ListPreAuthKeys = &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) } keys, err := h.GetPreAuthKeys(n) + if strings.HasPrefix(o, "json") { + jsonOutput(keys, err, o) + return + } + if err != nil { - fmt.Println(err) + fmt.Printf("Error getting the list of keys: %s\n", err) return } for _, k := range *keys { @@ -57,6 +64,7 @@ var CreatePreAuthKeyCmd = &cobra.Command{ if err != nil { log.Fatalf("Error getting namespace: %s", err) } + o, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { @@ -75,11 +83,15 @@ var CreatePreAuthKeyCmd = &cobra.Command{ expiration = &exp } - _, err = h.CreatePreAuthKey(n, reusable, expiration) + k, err := h.CreatePreAuthKey(n, reusable, expiration) + if strings.HasPrefix(o, "json") { + jsonOutput(k, err, o) + return + } if err != nil { fmt.Println(err) return } - fmt.Printf("Ook.\n") + fmt.Printf("Key: %s\n", k.Key) }, } diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index 067b6c24..bc043b64 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "log" + "strings" "github.com/spf13/cobra" ) @@ -26,16 +27,24 @@ var ListRoutesCmd = &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) } routes, err := h.GetNodeRoutes(n, args[0]) + + if strings.HasPrefix(o, "json") { + jsonOutput(routes, err, o) + return + + } if err != nil { fmt.Println(err) return } + fmt.Println(routes) }, } @@ -54,15 +63,22 @@ var EnableRouteCmd = &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.EnableNodeRoute(n, args[0], args[1]) + route, err := h.EnableNodeRoute(n, args[0], args[1]) + if strings.HasPrefix(o, "json") { + jsonOutput(route, err, o) + return + } + if err != nil { fmt.Println(err) return } + fmt.Printf("Enabled route %s\n", route) }, } From 8a207374c6a9948a9da88810d1e4b7fc8bf19c2e Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 13:59:18 +0200 Subject: [PATCH 5/8] Add some return when enabling routing succeedes + some comments... --- routes.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/routes.go b/routes.go index 2894366a..77bd72eb 100644 --- a/routes.go +++ b/routes.go @@ -26,18 +26,18 @@ func (h *Headscale) GetNodeRoutes(namespace string, nodeName string) (*[]netaddr // EnableNodeRoute enables a subnet route advertised by a node (identified by // namespace and node name) -func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr string) error { +func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr string) (*netaddr.IPPrefix, error) { m, err := h.GetMachine(namespace, nodeName) if err != nil { - return err + return nil, err } hi, err := m.GetHostInfo() if err != nil { - return err + return nil, err } route, err := netaddr.ParseIPPrefix(routeStr) if err != nil { - return err + return nil, err } for _, rIP := range hi.RoutableIPs { @@ -45,7 +45,7 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr db, err := h.db() if err != nil { log.Printf("Cannot open DB: %s", err) - return err + return nil, err } routes, _ := json.Marshal([]string{routeStr}) // TODO: only one for the time being, so overwriting the rest @@ -53,6 +53,10 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr db.Save(&m) db.Close() + // THIS IS COMPLETELY USELESS. + // The peers map is stored in memory in the server process. + // Definetely not accessible from the CLI tool. + // We need RPC to the server - or some kind of 'needsUpdate' field in the DB peers, _ := h.getPeers(*m) h.pollMu.Lock() for _, p := range *peers { @@ -61,9 +65,9 @@ func (h *Headscale) EnableNodeRoute(namespace string, nodeName string, routeStr } } h.pollMu.Unlock() - return nil + return &rIP, nil } } - return errors.New("could not find routable range") + return nil, errors.New("could not find routable range") } From 5cbd2d43a8fbc6c7fd7a7c0ba6474c91cbca39b7 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 14:12:22 +0200 Subject: [PATCH 6/8] Update README to mention json output + some minor updates --- README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a240308..8cb37e63 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Headscale implements this coordination server. - [x] Basic routing (advertise & accept) - [ ] Share nodes between ~~users~~ namespaces - [x] Node registration via pre-auth keys +- [X] JSON-formatted output - [ ] ACLs - [ ] DNS @@ -79,6 +80,22 @@ Suggestions/PRs welcomed! ./headscale -n myfirstnamespace node register YOURMACHINEKEY ``` +Alternatively, you can use Auth Keys to register your machines: + +1. Create an authkey + ```shell + ./headscale -n myfirstnamespace preauthkey create --reusable --expiration 24h + ``` + +2. Use the authkey from your machine to register it + ```shell + tailscale up -login-server YOUR_HEADSCALE_URL --authkey YOURAUTHKEY + ``` + + +Please bear in mind that all the commands from headscale support adding `-o json` or `-o json-line` to get a nicely JSON-formatted output. + + ## Configuration reference Headscale's configuration file is named `config.json` or `config.yaml`. Headscale will look for it in `/etc/headscale`, `~/.headscale` and finally the directory from where the Headscale binary is executed. @@ -131,7 +148,15 @@ To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/) ## Disclaimer -1. I have nothing to do with Tailscale, or Tailscale Inc. +1. We have nothing to do with Tailscale, or Tailscale Inc. 2. The purpose of writing this was to learn how Tailscale works. -3. I don't use Headscale myself. +3. ~~I don't use Headscale myself.~~ + + + +## More on Tailscale + +- https://tailscale.com/blog/how-tailscale-works/ +- https://tailscale.com/blog/tailscale-key-management/ +- https://tailscale.com/blog/an-unlikely-database-migration/ From 9eaac717624a07b711e6b1f39f24164788568578 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 16:56:27 +0200 Subject: [PATCH 7/8] Trim trailing spaces --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8cb37e63..2ec10fc1 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ An open source implementation of the Tailscale coordination server. ## Overview -Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/). +Tailscale is [a modern VPN](https://tailscale.com/) built on top of [Wireguard](https://www.wireguard.com/). It [works like an overlay network](https://tailscale.com/blog/how-tailscale-works/) between the computers of your networks - using all kinds of [NAT traversal sorcery](https://tailscale.com/blog/how-nat-traversal-works/). -Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'. +Everything in Tailscale is Open Source, except the GUI clients for proprietary OS (Windows and macOS/iOS), and the 'coordination/control server'. The control server works as an exchange point of cryptographic public keys for the nodes in the Tailscale network. It also assigns the IP addresses of the clients, creates the boundaries between each user, enables sharing machines between users, and exposes the advertised routes of your nodes. @@ -20,7 +20,7 @@ Headscale implements this coordination server. - [x] Node registration through the web flow - [x] Network changes are relied to the nodes - [x] ~~Multiuser~~ Namespace support -- [x] Basic routing (advertise & accept) +- [x] Basic routing (advertise & accept) - [ ] Share nodes between ~~users~~ namespaces - [x] Node registration via pre-auth keys - [X] JSON-formatted output @@ -43,10 +43,10 @@ Suggestions/PRs welcomed! ```shell make ``` - + 2. Get yourself a PostgreSQL DB running (yes, [I know](https://tailscale.com/blog/an-unlikely-database-migration/)) - ```shell + ```shell docker run --name headscale -e POSTGRES_DB=headscale -e \ POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -p 5432:5432 -d postgres ``` @@ -54,7 +54,7 @@ Suggestions/PRs welcomed! 3. Set some stuff up (headscale Wireguard keys & the config.json file) ```shell wg genkey > private.key - wg pubkey < private.key > public.key # not needed + wg pubkey < private.key > public.key # not needed cp config.json.example config.json ``` @@ -67,7 +67,7 @@ Suggestions/PRs welcomed! ```shell ./headscale serve ``` - + 6. Add your first machine ```shell tailscale up -login-server YOUR_HEADSCALE_URL @@ -148,15 +148,15 @@ To get a certificate automatically via [Let's Encrypt](https://letsencrypt.org/) ## Disclaimer -1. We have nothing to do with Tailscale, or Tailscale Inc. +1. We have nothing to do with Tailscale, or Tailscale Inc. 2. The purpose of writing this was to learn how Tailscale works. -3. ~~I don't use Headscale myself.~~ +3. ~~I don't use Headscale myself.~~ -## More on Tailscale +## More on Tailscale - https://tailscale.com/blog/how-tailscale-works/ - https://tailscale.com/blog/tailscale-key-management/ -- https://tailscale.com/blog/an-unlikely-database-migration/ +- https://tailscale.com/blog/an-unlikely-database-migration/ From 8ad366f977f00fd877493a00bc603249763ab958 Mon Sep 17 00:00:00 2001 From: Juan Font Alonso Date: Sat, 8 May 2021 17:06:36 +0200 Subject: [PATCH 8/8] Use JsonOutput in version too --- cmd/headscale/cli/namespaces.go | 4 ++-- cmd/headscale/cli/nodes.go | 4 ++-- cmd/headscale/cli/preauthkeys.go | 4 ++-- cmd/headscale/cli/routes.go | 6 +++--- cmd/headscale/cli/utils.go | 2 +- cmd/headscale/headscale.go | 24 ++++-------------------- 6 files changed, 14 insertions(+), 30 deletions(-) diff --git a/cmd/headscale/cli/namespaces.go b/cmd/headscale/cli/namespaces.go index 9f4a66ee..1fb2de69 100644 --- a/cmd/headscale/cli/namespaces.go +++ b/cmd/headscale/cli/namespaces.go @@ -30,7 +30,7 @@ var CreateNamespaceCmd = &cobra.Command{ } namespace, err := h.CreateNamespace(args[0]) if strings.HasPrefix(o, "json") { - jsonOutput(namespace, err, o) + JsonOutput(namespace, err, o) return } if err != nil { @@ -52,7 +52,7 @@ var ListNamespacesCmd = &cobra.Command{ } namespaces, err := h.ListNamespaces() if strings.HasPrefix(o, "json") { - jsonOutput(namespaces, err, o) + JsonOutput(namespaces, err, o) return } if err != nil { diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 36bc29d0..de76a407 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -30,7 +30,7 @@ var RegisterCmd = &cobra.Command{ } m, err := h.RegisterMachine(args[0], n) if strings.HasPrefix(o, "json") { - jsonOutput(m, err, o) + JsonOutput(m, err, o) return } if err != nil { @@ -57,7 +57,7 @@ var ListNodesCmd = &cobra.Command{ } machines, err := h.ListMachinesInNamespace(n) if strings.HasPrefix(o, "json") { - jsonOutput(machines, err, o) + JsonOutput(machines, err, o) return } diff --git a/cmd/headscale/cli/preauthkeys.go b/cmd/headscale/cli/preauthkeys.go index ae6c4682..3db09f95 100644 --- a/cmd/headscale/cli/preauthkeys.go +++ b/cmd/headscale/cli/preauthkeys.go @@ -31,7 +31,7 @@ var ListPreAuthKeys = &cobra.Command{ } keys, err := h.GetPreAuthKeys(n) if strings.HasPrefix(o, "json") { - jsonOutput(keys, err, o) + JsonOutput(keys, err, o) return } @@ -85,7 +85,7 @@ var CreatePreAuthKeyCmd = &cobra.Command{ k, err := h.CreatePreAuthKey(n, reusable, expiration) if strings.HasPrefix(o, "json") { - jsonOutput(k, err, o) + JsonOutput(k, err, o) return } if err != nil { diff --git a/cmd/headscale/cli/routes.go b/cmd/headscale/cli/routes.go index bc043b64..007bc6c2 100644 --- a/cmd/headscale/cli/routes.go +++ b/cmd/headscale/cli/routes.go @@ -36,10 +36,10 @@ var ListRoutesCmd = &cobra.Command{ routes, err := h.GetNodeRoutes(n, args[0]) if strings.HasPrefix(o, "json") { - jsonOutput(routes, err, o) + JsonOutput(routes, err, o) return - } + if err != nil { fmt.Println(err) return @@ -71,7 +71,7 @@ var EnableRouteCmd = &cobra.Command{ } route, err := h.EnableNodeRoute(n, args[0], args[1]) if strings.HasPrefix(o, "json") { - jsonOutput(route, err, o) + JsonOutput(route, err, o) return } diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index 0738e18b..a9897fb4 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -79,7 +79,7 @@ func loadDerpMap(path string) (*tailcfg.DERPMap, error) { return &derpMap, err } -func jsonOutput(result interface{}, errResult error, outputFormat string) { +func JsonOutput(result interface{}, errResult error, outputFormat string) { var j []byte var err error switch outputFormat { diff --git a/cmd/headscale/headscale.go b/cmd/headscale/headscale.go index 974227ea..ccc75e8f 100644 --- a/cmd/headscale/headscale.go +++ b/cmd/headscale/headscale.go @@ -1,7 +1,6 @@ package main import ( - "encoding/json" "errors" "fmt" "log" @@ -20,27 +19,12 @@ var versionCmd = &cobra.Command{ Short: "Print the version.", Long: "The version of headscale.", Run: func(cmd *cobra.Command, args []string) { - 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) + if strings.HasPrefix(o, "json") { + cli.JsonOutput(map[string]string{"version": version}, nil, o) + return } + fmt.Println(version) }, }