diff --git a/cmd/headscale/cli/nodes.go b/cmd/headscale/cli/nodes.go index 0abe87b7..c734e8ed 100644 --- a/cmd/headscale/cli/nodes.go +++ b/cmd/headscale/cli/nodes.go @@ -46,6 +46,18 @@ func init() { log.Fatalf(err.Error()) } nodeCmd.AddCommand(deleteNodeCmd) + + moveNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)") + err = moveNodeCmd.MarkFlagRequired("identifier") + if err != nil { + log.Fatalf(err.Error()) + } + moveNodeCmd.Flags().StringP("namespace", "n", "", "New namespace") + err = moveNodeCmd.MarkFlagRequired("namespace") + if err != nil { + log.Fatalf(err.Error()) + } + nodeCmd.AddCommand(moveNodeCmd) } var nodeCmd = &cobra.Command{ @@ -296,6 +308,101 @@ var deleteNodeCmd = &cobra.Command{ }, } +var moveNodeCmd = &cobra.Command{ + Use: "move", + Short: "Move node to another namespace", + Aliases: []string{"mv"}, + Run: func(cmd *cobra.Command, args []string) { + output, _ := cmd.Flags().GetString("output") + + identifier, err := cmd.Flags().GetUint64("identifier") + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Error converting ID to integer: %s", err), + output, + ) + + return + } + + namespace, err := cmd.Flags().GetString("namespace") + if err != nil { + ErrorOutput( + err, + fmt.Sprintf("Error getting namespace: %s", err), + output, + ) + + return + } + + ctx, client, conn, cancel := getHeadscaleCLIClient() + defer cancel() + defer conn.Close() + + getRequest := &v1.GetMachineRequest{ + MachineId: identifier, + } + + _, err = client.GetMachine(ctx, getRequest) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf( + "Error getting node: %s", + status.Convert(err).Message(), + ), + output, + ) + + return + } + + moveRequest := &v1.MoveMachineRequest{ + MachineId: identifier, + Namespace: namespace, + } + + moveResponse, err := client.MoveMachine(ctx, moveRequest) + if err != nil { + ErrorOutput( + err, + fmt.Sprintf( + "Error moving node: %s", + status.Convert(err).Message(), + ), + output, + ) + + return + } + + if output != "" { + SuccessOutput(moveResponse, "", output) + + return + } + if err != nil { + ErrorOutput( + err, + fmt.Sprintf( + "Error moving node: %s", + status.Convert(err).Message(), + ), + output, + ) + + return + } + SuccessOutput( + map[string]string{"Result": "Node moved to another namespace"}, + "Node moved to another namespace", + output, + ) + }, +} + func nodesToPtables( currentNamespace string, machines []*v1.Machine, diff --git a/grpcv1.go b/grpcv1.go index 647e599d..b8348558 100644 --- a/grpcv1.go +++ b/grpcv1.go @@ -253,6 +253,23 @@ func (api headscaleV1APIServer) ListMachines( return &v1.ListMachinesResponse{Machines: response}, nil } +func (api headscaleV1APIServer) MoveMachine( + ctx context.Context, + request *v1.MoveMachineRequest, +) (*v1.MoveMachineResponse, error) { + machine, err := api.h.GetMachineByID(request.GetMachineId()) + if err != nil { + return nil, err + } + + err = api.h.SetMachineNamespace(machine, request.GetNamespace()) + if err != nil { + return nil, err + } + + return &v1.MoveMachineResponse{Machine: machine.toProto()}, nil +} + func (api headscaleV1APIServer) GetMachineRoute( ctx context.Context, request *v1.GetMachineRouteRequest,