diff --git a/api.go b/api.go index a31cf52..0c1f3d4 100644 --- a/api.go +++ b/api.go @@ -43,7 +43,7 @@ func (h *Headscale) RegisterWebAPI(c *gin.Context) {

- headscale -n NAMESPACE nodes register %s + headscale -n NAMESPACE nodes register -k %s

diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index c44aa5e..cdf37ef 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -17,15 +17,50 @@ import ( func init() { rootCmd.AddCommand(nodeCmd) - nodeCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace") - err := nodeCmd.MarkPersistentFlagRequired("namespace") + listNodesCmd.Flags().StringP("namespace", "n", "", "Filter by namespace") + nodeCmd.AddCommand(listNodesCmd) + + registerNodeCmd.Flags().StringP("namespace", "n", "", "Namespace") + err := registerNodeCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } + registerNodeCmd.Flags().StringP("key", "k", "", "Key") + err = registerNodeCmd.MarkFlagRequired("key") if err != nil { log.Fatalf(err.Error()) } - nodeCmd.AddCommand(listNodesCmd) nodeCmd.AddCommand(registerNodeCmd) + + deleteNodeCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = deleteNodeCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(deleteNodeCmd) + + shareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") + err = shareMachineCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } + shareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = shareMachineCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(shareMachineCmd) + + unshareMachineCmd.Flags().StringP("namespace", "n", "", "Namespace") + err = unshareMachineCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } + unshareMachineCmd.Flags().IntP("identifier", "i", 0, "Node identifier (ID)") + err = unshareMachineCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } nodeCmd.AddCommand(unshareMachineCmd) } @@ -35,14 +70,8 @@ var nodeCmd = &cobra.Command{ } var registerNodeCmd = &cobra.Command{ - Use: "register machineID", + Use: "register", Short: "Registers a machine to your network", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { n, err := cmd.Flags().GetString("namespace") if err != nil { @@ -54,7 +83,11 @@ var registerNodeCmd = &cobra.Command{ if err != nil { log.Fatalf("Error initializing: %s", err) } - m, err := h.RegisterMachine(args[0], n) + machineIDStr, err := cmd.Flags().GetString("key") + if err != nil { + log.Fatalf("Error getting machine ID: %s", err) + } + m, err := h.RegisterMachine(machineIDStr, n) if strings.HasPrefix(o, "json") { JsonOutput(m, err, o) return @@ -69,7 +102,7 @@ var registerNodeCmd = &cobra.Command{ var listNodesCmd = &cobra.Command{ Use: "list", - Short: "List the nodes in a given namespace", + Short: "List nodes", Run: func(cmd *cobra.Command, args []string) { n, err := cmd.Flags().GetString("namespace") if err != nil { @@ -82,23 +115,44 @@ var listNodesCmd = &cobra.Command{ log.Fatalf("Error initializing: %s", err) } - namespace, err := h.GetNamespace(n) - if err != nil { - log.Fatalf("Error fetching namespace: %s", err) + var namespaces []headscale.Namespace + var namespace *headscale.Namespace + var sharedMachines *[]headscale.Machine + if len(n) == 0 { + // no namespace provided, list all + tmp, err := h.ListNamespaces() + if err != nil { + log.Fatalf("Error fetching namespace: %s", err) + } + namespaces = *tmp + } else { + namespace, err = h.GetNamespace(n) + if err != nil { + log.Fatalf("Error fetching namespace: %s", err) + } + namespaces = append(namespaces, *namespace) + + sharedMachines, err = h.ListSharedMachinesInNamespace(n) + if err != nil { + log.Fatalf("Error fetching shared machines: %s", err) + } } - machines, err := h.ListMachinesInNamespace(n) - if err != nil { - log.Fatalf("Error fetching machines: %s", err) + var allMachines []headscale.Machine + for _, namespace := range namespaces { + machines, err := h.ListMachinesInNamespace(namespace.Name) + if err != nil { + log.Fatalf("Error fetching machines: %s", err) + } + allMachines = append(allMachines, *machines...) } - sharedMachines, err := h.ListSharedMachinesInNamespace(n) - if err != nil { - log.Fatalf("Error fetching shared machines: %s", err) + // listing sharedMachines is only relevant when a particular namespace is + // requested + if sharedMachines != nil { + allMachines = append(allMachines, *sharedMachines...) } - allMachines := append(*machines, *sharedMachines...) - if strings.HasPrefix(o, "json") { JsonOutput(allMachines, err, o) return @@ -108,7 +162,7 @@ var listNodesCmd = &cobra.Command{ log.Fatalf("Error getting nodes: %s", err) } - d, err := nodesToPtables(*namespace, allMachines) + d, err := nodesToPtables(namespace, allMachines) if err != nil { log.Fatalf("Error converting to table: %s", err) } @@ -121,21 +175,15 @@ var listNodesCmd = &cobra.Command{ } var deleteNodeCmd = &cobra.Command{ - Use: "delete ID", + Use: "delete", Short: "Delete a node", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { output, _ := cmd.Flags().GetString("output") h, err := getHeadscaleApp() if err != nil { log.Fatalf("Error initializing: %s", err) } - id, err := strconv.Atoi(args[0]) + id, err := cmd.Flags().GetInt("identifier") if err != nil { log.Fatalf("Error converting ID to integer: %s", err) } @@ -176,47 +224,42 @@ var deleteNodeCmd = &cobra.Command{ }, } +func sharingWorker(cmd *cobra.Command, args []string) (*headscale.Headscale, string, *headscale.Machine, *headscale.Namespace) { + namespaceStr, err := cmd.Flags().GetString("namespace") + if err != nil { + log.Fatalf("Error getting namespace: %s", err) + } + + output, _ := cmd.Flags().GetString("output") + + h, err := getHeadscaleApp() + if err != nil { + log.Fatalf("Error initializing: %s", err) + } + + namespace, err := h.GetNamespace(namespaceStr) + if err != nil { + log.Fatalf("Error fetching namespace %s: %s", namespaceStr, err) + } + + id, err := cmd.Flags().GetInt("identifier") + if err != nil { + log.Fatalf("Error converting ID to integer: %s", err) + } + machine, err := h.GetMachineByID(uint64(id)) + if err != nil { + log.Fatalf("Error getting node: %s", err) + } + + return h, output, machine, namespace +} + var shareMachineCmd = &cobra.Command{ - Use: "share ID namespace", + Use: "share", Short: "Shares a node from the current namespace to the specified one", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 2 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatalf("Error getting namespace: %s", err) - } - output, _ := cmd.Flags().GetString("output") - - h, err := getHeadscaleApp() - if err != nil { - log.Fatalf("Error initializing: %s", err) - } - - _, err = h.GetNamespace(namespace) - if err != nil { - log.Fatalf("Error fetching origin namespace: %s", err) - } - - destinationNamespace, err := h.GetNamespace(args[1]) - if err != nil { - log.Fatalf("Error fetching destination namespace: %s", err) - } - - id, err := strconv.Atoi(args[0]) - if err != nil { - log.Fatalf("Error converting ID to integer: %s", err) - } - machine, err := h.GetMachineByID(uint64(id)) - if err != nil { - log.Fatalf("Error getting node: %s", err) - } - - err = h.AddSharedMachineToNamespace(machine, destinationNamespace) + h, output, machine, namespace := sharingWorker(cmd, args) + err := h.AddSharedMachineToNamespace(machine, namespace) if strings.HasPrefix(output, "json") { JsonOutput(map[string]string{"Result": "Node shared"}, err, output) return @@ -231,41 +274,11 @@ var shareMachineCmd = &cobra.Command{ } var unshareMachineCmd = &cobra.Command{ - Use: "unshare ID", + Use: "unshare", Short: "Unshares a node from the specified namespace", - Args: func(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return fmt.Errorf("missing parameters") - } - return nil - }, Run: func(cmd *cobra.Command, args []string) { - namespace, err := cmd.Flags().GetString("namespace") - if err != nil { - log.Fatalf("Error getting namespace: %s", err) - } - output, _ := cmd.Flags().GetString("output") - - h, err := getHeadscaleApp() - if err != nil { - log.Fatalf("Error initializing: %s", err) - } - - n, err := h.GetNamespace(namespace) - if err != nil { - log.Fatalf("Error fetching namespace: %s", err) - } - - id, err := strconv.Atoi(args[0]) - if err != nil { - log.Fatalf("Error converting ID to integer: %s", err) - } - machine, err := h.GetMachineByID(uint64(id)) - if err != nil { - log.Fatalf("Error getting node: %s", err) - } - - err = h.RemoveSharedMachineFromNamespace(machine, n) + h, output, machine, namespace := sharingWorker(cmd, args) + err := h.RemoveSharedMachineFromNamespace(machine, namespace) if strings.HasPrefix(output, "json") { JsonOutput(map[string]string{"Result": "Node unshared"}, err, output) return @@ -279,7 +292,7 @@ var unshareMachineCmd = &cobra.Command{ }, } -func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) { +func nodesToPtables(currentNamespace *headscale.Namespace, machines []headscale.Machine) (pterm.TableData, error) { d := pterm.TableData{{"ID", "Name", "NodeKey", "Namespace", "IP address", "Ephemeral", "Last seen", "Online"}} for _, machine := range machines { @@ -307,9 +320,10 @@ func nodesToPtables(currentNamespace headscale.Namespace, machines []headscale.M } var namespace string - if currentNamespace.ID == machine.NamespaceID { + if (currentNamespace == nil) || (currentNamespace.ID == machine.NamespaceID) { namespace = pterm.LightMagenta(machine.Namespace.Name) } else { + // Shared into this namespace namespace = pterm.LightYellow(machine.Namespace.Name) } d = append(d, []string{strconv.FormatUint(machine.ID, 10), machine.Name, nodeKey.ShortString(), namespace, machine.IPAddress, strconv.FormatBool(ephemeral), lastSeenTime, online}) diff --git a/docs/Running.md b/docs/Running.md index 0837365..dbb8704 100644 --- a/docs/Running.md +++ b/docs/Running.md @@ -97,7 +97,7 @@ 9. In the server, register your machine to a namespace with the CLI ```shell - headscale -n myfirstnamespace nodes register YOURMACHINEKEY + headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` or docker: ```shell @@ -106,11 +106,11 @@ -v $(pwd)/config.json:/config.json \ -v $(pwd)/derp.yaml:/derp.yaml \ headscale/headscale:x.x.x \ - headscale -n myfirstnamespace nodes register YOURMACHINEKEY + headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` or if your server is already running in docker: ```shell - docker exec headscale -n myfirstnamespace nodes register YOURMACHINEKEY + docker exec headscale -n myfirstnamespace nodes register -k YOURMACHINEKEY ``` Alternatively, you can use Auth Keys to register your machines: diff --git a/integration_test.go b/integration_test.go index 5309242..f73d76f 100644 --- a/integration_test.go +++ b/integration_test.go @@ -493,7 +493,7 @@ func (s *IntegrationTestSuite) TestSharedNodes() { result, err := executeCommand( &headscale, - []string{"headscale", "nodes", "share", "--namespace", "shared", fmt.Sprint(machine.ID), "main"}, + []string{"headscale", "nodes", "share", "--identifier", fmt.Sprint(machine.ID), "--namespace", "main"}, []string{}, ) assert.Nil(s.T(), err) diff --git a/sharing.go b/sharing.go index 879ed06..5f6a8f4 100644 --- a/sharing.go +++ b/sharing.go @@ -43,7 +43,8 @@ func (h *Headscale) AddSharedMachineToNamespace(m *Machine, ns *Namespace) error // RemoveSharedMachineFromNamespace removes a shared machine from a namespace func (h *Headscale) RemoveSharedMachineFromNamespace(m *Machine, ns *Namespace) error { if m.NamespaceID == ns.ID { - return errorSameNamespace + // Can't unshare from primary namespace + return errorMachineNotShared } sharedMachine := SharedMachine{} diff --git a/sharing_test.go b/sharing_test.go index 140b05f..1133fd9 100644 --- a/sharing_test.go +++ b/sharing_test.go @@ -86,6 +86,9 @@ func (s *Suite) TestUnshare(c *check.C) { err = h.RemoveSharedMachineFromNamespace(m2, n1) c.Assert(err, check.Equals, errorMachineNotShared) + + err = h.RemoveSharedMachineFromNamespace(m1, n1) + c.Assert(err, check.Equals, errorMachineNotShared) } func (s *Suite) TestAlreadyShared(c *check.C) {