This commit is contained in:
Kristoffer Dalby 2025-07-15 14:51:23 +00:00
parent 024ed59ea9
commit 8253d588c6
31 changed files with 300 additions and 364 deletions

View File

@ -41,6 +41,47 @@ systemctl start headscale
### BREAKING ### BREAKING
- **CLI: Remove deprecated flags**
- `--identifier` flag removed - use `--node` or `--user` instead
- `--namespace` flag removed - use `--user` instead
**Command changes:**
```bash
# Before
headscale nodes expire --identifier 123
headscale nodes rename --identifier 123 new-name
headscale nodes delete --identifier 123
headscale nodes move --identifier 123 --user 456
headscale nodes list-routes --identifier 123
# After
headscale nodes expire --node 123
headscale nodes rename --node 123 new-name
headscale nodes delete --node 123
headscale nodes move --node 123 --user 456
headscale nodes list-routes --node 123
# Before
headscale users destroy --identifier 123
headscale users rename --identifier 123 --new-name john
headscale users list --identifier 123
# After
headscale users destroy --user 123
headscale users rename --user 123 --new-name john
headscale users list --user 123
# Before
headscale nodes register --namespace myuser nodekey
headscale nodes list --namespace myuser
headscale preauthkeys create --namespace myuser
# After
headscale nodes register --user myuser nodekey
headscale nodes list --user myuser
headscale preauthkeys create --user myuser
```
- Policy: Zero or empty destination port is no longer allowed - Policy: Zero or empty destination port is no longer allowed
[#2606](https://github.com/juanfont/headscale/pull/2606) [#2606](https://github.com/juanfont/headscale/pull/2606)

View File

@ -15,7 +15,6 @@ import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
func init() { func init() {
rootCmd.AddCommand(apiKeysCmd) rootCmd.AddCommand(apiKeysCmd)
apiKeysCmd.AddCommand(listAPIKeys) apiKeysCmd.AddCommand(listAPIKeys)
@ -98,7 +97,6 @@ var listAPIKeys = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -148,7 +146,6 @@ If you loose a key, create a new one and revoke (expire) the old one.`,
SuccessOutput(response.GetApiKey(), response.GetApiKey(), output) SuccessOutput(response.GetApiKey(), response.GetApiKey(), output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -185,7 +182,6 @@ var expireAPIKeyCmd = &cobra.Command{
SuccessOutput(response, "Key expired", output) SuccessOutput(response, "Key expired", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -222,7 +218,6 @@ var deleteAPIKeyCmd = &cobra.Command{
SuccessOutput(response, "Key deleted", output) SuccessOutput(response, "Key deleted", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }

View File

@ -15,11 +15,6 @@ const (
errPreAuthKeyMalformed = Error("key is malformed. expected 64 hex characters with `nodekey` prefix") errPreAuthKeyMalformed = Error("key is malformed. expected 64 hex characters with `nodekey` prefix")
) )
// Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors
type Error string
func (e Error) Error() string { return string(e) }
func init() { func init() {
rootCmd.AddCommand(debugCmd) rootCmd.AddCommand(debugCmd)
@ -30,11 +25,6 @@ func init() {
} }
createNodeCmd.Flags().StringP("user", "u", "", "User") createNodeCmd.Flags().StringP("user", "u", "", "User")
createNodeCmd.Flags().StringP("namespace", "n", "", "User")
createNodeNamespaceFlag := createNodeCmd.Flags().Lookup("namespace")
createNodeNamespaceFlag.Deprecated = deprecateNamespaceMessage
createNodeNamespaceFlag.Hidden = true
err = createNodeCmd.MarkFlagRequired("user") err = createNodeCmd.MarkFlagRequired("user")
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("") log.Fatal().Err(err).Msg("")
@ -60,7 +50,7 @@ var createNodeCmd = &cobra.Command{
Use: "create-node", Use: "create-node",
Short: "Create a node that can be registered with `nodes register <>` command", Short: "Create a node that can be registered with `nodes register <>` command",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
user, err := cmd.Flags().GetString("user") user, err := cmd.Flags().GetString("user")
if err != nil { if err != nil {
@ -129,7 +119,6 @@ var createNodeCmd = &cobra.Command{
SuccessOutput(response.GetNode(), "Node created", output) SuccessOutput(response.GetNode(), "Node created", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }

View File

@ -63,12 +63,6 @@ func TestCreateNodeCommandFlags(t *testing.T) {
assert.NotNil(t, routeFlag) assert.NotNil(t, routeFlag)
assert.Equal(t, "r", routeFlag.Shorthand) assert.Equal(t, "r", routeFlag.Shorthand)
// Test deprecated namespace flag
namespaceFlag := createNodeCmd.Flags().Lookup("namespace")
assert.NotNil(t, namespaceFlag)
assert.Equal(t, "n", namespaceFlag.Shorthand)
assert.True(t, namespaceFlag.Hidden, "Namespace flag should be hidden")
assert.Equal(t, deprecateNamespaceMessage, namespaceFlag.Deprecated)
} }
func TestCreateNodeCommandRequiredFlags(t *testing.T) { func TestCreateNodeCommandRequiredFlags(t *testing.T) {
@ -134,8 +128,6 @@ func TestCreateNodeCommandFlagDescriptions(t *testing.T) {
routeFlag := createNodeCmd.Flags().Lookup("route") routeFlag := createNodeCmd.Flags().Lookup("route")
assert.Contains(t, routeFlag.Usage, "routes to advertise") assert.Contains(t, routeFlag.Usage, "routes to advertise")
namespaceFlag := createNodeCmd.Flags().Lookup("namespace")
assert.Equal(t, "User", namespaceFlag.Usage) // Same as user flag
} }
// Note: We can't easily test the actual execution of create-node because: // Note: We can't easily test the actual execution of create-node because:

View File

@ -22,7 +22,7 @@ var generatePrivateKeyCmd = &cobra.Command{
Use: "private-key", Use: "private-key",
Short: "Generate a private key for the headscale server", Short: "Generate a private key for the headscale server",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
machineKey := key.NewMachine() machineKey := key.NewMachine()
machineKeyStr, err := machineKey.MarshalText() machineKeyStr, err := machineKey.MarshalText()

View File

@ -15,6 +15,11 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// Error is used to compare errors as per https://dave.cheney.net/2016/04/07/constant-errors
type Error string
func (e Error) Error() string { return string(e) }
const ( const (
errMockOidcClientIDNotDefined = Error("MOCKOIDC_CLIENT_ID not defined") errMockOidcClientIDNotDefined = Error("MOCKOIDC_CLIENT_ID not defined")
errMockOidcClientSecretNotDefined = Error("MOCKOIDC_CLIENT_SECRET not defined") errMockOidcClientSecretNotDefined = Error("MOCKOIDC_CLIENT_SECRET not defined")

View File

@ -32,27 +32,13 @@ func init() {
// Display options // Display options
listNodesCmd.Flags().BoolP("tags", "t", false, "Show tags") listNodesCmd.Flags().BoolP("tags", "t", false, "Show tags")
listNodesCmd.Flags().String("columns", "", "Comma-separated list of columns to display") listNodesCmd.Flags().String("columns", "", "Comma-separated list of columns to display")
// Backward compatibility
listNodesCmd.Flags().StringP("namespace", "n", "", "User")
listNodesNamespaceFlag := listNodesCmd.Flags().Lookup("namespace")
listNodesNamespaceFlag.Deprecated = deprecateNamespaceMessage
listNodesNamespaceFlag.Hidden = true
nodeCmd.AddCommand(listNodesCmd) nodeCmd.AddCommand(listNodesCmd)
listNodeRoutesCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)") listNodeRoutesCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
listNodeRoutesCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID) - deprecated, use --node")
identifierFlag := listNodeRoutesCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --node"
identifierFlag.Hidden = true
nodeCmd.AddCommand(listNodeRoutesCmd) nodeCmd.AddCommand(listNodeRoutesCmd)
registerNodeCmd.Flags().StringP("user", "u", "", "User") registerNodeCmd.Flags().StringP("user", "u", "", "User")
registerNodeCmd.Flags().StringP("namespace", "n", "", "User")
registerNodeNamespaceFlag := registerNodeCmd.Flags().Lookup("namespace")
registerNodeNamespaceFlag.Deprecated = deprecateNamespaceMessage
registerNodeNamespaceFlag.Hidden = true
err := registerNodeCmd.MarkFlagRequired("user") err := registerNodeCmd.MarkFlagRequired("user")
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
@ -65,40 +51,24 @@ func init() {
nodeCmd.AddCommand(registerNodeCmd) nodeCmd.AddCommand(registerNodeCmd)
expireNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)") expireNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
expireNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID) - deprecated, use --node")
identifierFlag = expireNodeCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --node"
identifierFlag.Hidden = true
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
nodeCmd.AddCommand(expireNodeCmd) nodeCmd.AddCommand(expireNodeCmd)
renameNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)") renameNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
renameNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID) - deprecated, use --node")
identifierFlag = renameNodeCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --node"
identifierFlag.Hidden = true
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
nodeCmd.AddCommand(renameNodeCmd) nodeCmd.AddCommand(renameNodeCmd)
deleteNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)") deleteNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
deleteNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID) - deprecated, use --node")
identifierFlag = deleteNodeCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --node"
identifierFlag.Hidden = true
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
nodeCmd.AddCommand(deleteNodeCmd) nodeCmd.AddCommand(deleteNodeCmd)
moveNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)") moveNodeCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
moveNodeCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID) - deprecated, use --node")
identifierFlag = moveNodeCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --node"
identifierFlag.Hidden = true
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
@ -106,24 +76,19 @@ func init() {
moveNodeCmd.Flags().Uint64P("user", "u", 0, "New user") moveNodeCmd.Flags().Uint64P("user", "u", 0, "New user")
moveNodeCmd.Flags().StringP("namespace", "n", "", "User")
moveNodeNamespaceFlag := moveNodeCmd.Flags().Lookup("namespace")
moveNodeNamespaceFlag.Deprecated = deprecateNamespaceMessage
moveNodeNamespaceFlag.Hidden = true
err = moveNodeCmd.MarkFlagRequired("user") err = moveNodeCmd.MarkFlagRequired("user")
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())
} }
nodeCmd.AddCommand(moveNodeCmd) nodeCmd.AddCommand(moveNodeCmd)
tagCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)") tagCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
tagCmd.MarkFlagRequired("identifier") tagCmd.MarkFlagRequired("node")
tagCmd.Flags().StringSliceP("tags", "t", []string{}, "List of tags to add to the node") tagCmd.Flags().StringSliceP("tags", "t", []string{}, "List of tags to add to the node")
nodeCmd.AddCommand(tagCmd) nodeCmd.AddCommand(tagCmd)
approveRoutesCmd.Flags().Uint64P("identifier", "i", 0, "Node identifier (ID)") approveRoutesCmd.Flags().StringP("node", "n", "", "Node identifier (ID, name, hostname, or IP)")
approveRoutesCmd.MarkFlagRequired("identifier") approveRoutesCmd.MarkFlagRequired("node")
approveRoutesCmd.Flags().StringSliceP("routes", "r", []string{}, `List of routes that will be approved (comma-separated, e.g. "10.0.0.0/8,192.168.0.0/24" or empty string to remove all approved routes)`) approveRoutesCmd.Flags().StringSliceP("routes", "r", []string{}, `List of routes that will be approved (comma-separated, e.g. "10.0.0.0/8,192.168.0.0/24" or empty string to remove all approved routes)`)
nodeCmd.AddCommand(approveRoutesCmd) nodeCmd.AddCommand(approveRoutesCmd)
@ -140,7 +105,7 @@ var registerNodeCmd = &cobra.Command{
Use: "register", Use: "register",
Short: "Registers a node to your network", Short: "Registers a node to your network",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
user, err := cmd.Flags().GetString("user") user, err := cmd.Flags().GetString("user")
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
@ -181,7 +146,6 @@ var registerNodeCmd = &cobra.Command{
fmt.Sprintf("Node %s registered", response.GetNode().GetGivenName()), output) fmt.Sprintf("Node %s registered", response.GetNode().GetGivenName()), output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -207,9 +171,6 @@ var listNodesCmd = &cobra.Command{
if user, _ := cmd.Flags().GetString("user"); user != "" { if user, _ := cmd.Flags().GetString("user"); user != "" {
request.User = user request.User = user
} }
if namespace, _ := cmd.Flags().GetString("namespace"); namespace != "" {
request.User = namespace // backward compatibility
}
// Handle node filtering (new functionality) // Handle node filtering (new functionality)
if nodeFlag, _ := cmd.Flags().GetString("node"); nodeFlag != "" { if nodeFlag, _ := cmd.Flags().GetString("node"); nodeFlag != "" {
@ -267,7 +228,6 @@ var listNodesCmd = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -279,7 +239,7 @@ var listNodeRoutesCmd = &cobra.Command{
Short: "List routes available on nodes", Short: "List routes available on nodes",
Aliases: []string{"lsr", "routes"}, Aliases: []string{"lsr", "routes"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
if err != nil { if err != nil {
ErrorOutput( ErrorOutput(
@ -339,7 +299,6 @@ var listNodeRoutesCmd = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -352,7 +311,7 @@ var expireNodeCmd = &cobra.Command{
Long: "Expiring a node will keep the node in the database and force it to reauthenticate.", Long: "Expiring a node will keep the node in the database and force it to reauthenticate.",
Aliases: []string{"logout", "exp", "e"}, Aliases: []string{"logout", "exp", "e"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
if err != nil { if err != nil {
@ -385,7 +344,6 @@ var expireNodeCmd = &cobra.Command{
SuccessOutput(response.GetNode(), "Node expired", output) SuccessOutput(response.GetNode(), "Node expired", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -396,7 +354,7 @@ var renameNodeCmd = &cobra.Command{
Use: "rename NEW_NAME", Use: "rename NEW_NAME",
Short: "Renames a node in your network", Short: "Renames a node in your network",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
if err != nil { if err != nil {
@ -435,7 +393,6 @@ var renameNodeCmd = &cobra.Command{
SuccessOutput(response.GetNode(), "Node renamed", output) SuccessOutput(response.GetNode(), "Node renamed", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -447,7 +404,7 @@ var deleteNodeCmd = &cobra.Command{
Short: "Delete a node", Short: "Delete a node",
Aliases: []string{"del"}, Aliases: []string{"del"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
if err != nil { if err != nil {
@ -477,7 +434,6 @@ var deleteNodeCmd = &cobra.Command{
nodeName = getResponse.GetNode().GetName() nodeName = getResponse.GetNode().GetName()
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -523,7 +479,6 @@ var deleteNodeCmd = &cobra.Command{
) )
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -538,7 +493,7 @@ var moveNodeCmd = &cobra.Command{
Short: "Move node to another user", Short: "Move node to another user",
Aliases: []string{"mv"}, Aliases: []string{"mv"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
if err != nil { if err != nil {
@ -593,7 +548,6 @@ var moveNodeCmd = &cobra.Command{
SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output) SuccessOutput(moveResponse.GetNode(), "Node moved to another user", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -618,7 +572,7 @@ it can be run to remove the IPs that should no longer
be assigned to nodes.`, be assigned to nodes.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
var err error var err error
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
confirm := false confirm := false
prompt := &survey.Confirm{ prompt := &survey.Confirm{
@ -643,7 +597,6 @@ be assigned to nodes.`,
SuccessOutput(changes, "Node IPs backfilled successfully", output) SuccessOutput(changes, "Node IPs backfilled successfully", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -829,7 +782,7 @@ var tagCmd = &cobra.Command{
Short: "Manage the tags of a node", Short: "Manage the tags of a node",
Aliases: []string{"tags", "t"}, Aliases: []string{"tags", "t"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
// retrieve flags from CLI // retrieve flags from CLI
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
@ -876,7 +829,6 @@ var tagCmd = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -887,7 +839,7 @@ var approveRoutesCmd = &cobra.Command{
Use: "approve-routes", Use: "approve-routes",
Short: "Manage the approved routes of a node", Short: "Manage the approved routes of a node",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
// retrieve flags from CLI // retrieve flags from CLI
identifier, err := GetNodeIdentifier(cmd) identifier, err := GetNodeIdentifier(cmd)
@ -934,7 +886,6 @@ var approveRoutesCmd = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }

View File

@ -41,7 +41,7 @@ var getPolicy = &cobra.Command{
Short: "Print the current ACL Policy", Short: "Print the current ACL Policy",
Aliases: []string{"show", "view", "fetch"}, Aliases: []string{"show", "view", "fetch"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
err := WithClient(func(ctx context.Context, client v1.HeadscaleServiceClient) error { err := WithClient(func(ctx context.Context, client v1.HeadscaleServiceClient) error {
request := &v1.GetPolicyRequest{} request := &v1.GetPolicyRequest{}
@ -58,7 +58,6 @@ var getPolicy = &cobra.Command{
SuccessOutput("", response.GetPolicy(), "") SuccessOutput("", response.GetPolicy(), "")
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -73,7 +72,7 @@ var setPolicy = &cobra.Command{
This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`, This command only works when the acl.policy_mode is set to "db", and the policy will be stored in the database.`,
Aliases: []string{"put", "update"}, Aliases: []string{"put", "update"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
policyPath, _ := cmd.Flags().GetString("file") policyPath, _ := cmd.Flags().GetString("file")
f, err := os.Open(policyPath) f, err := os.Open(policyPath)
@ -100,7 +99,6 @@ var setPolicy = &cobra.Command{
SuccessOutput(nil, "Policy updated.", "") SuccessOutput(nil, "Policy updated.", "")
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -111,23 +109,26 @@ var checkPolicy = &cobra.Command{
Use: "check", Use: "check",
Short: "Check the Policy file for errors", Short: "Check the Policy file for errors",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
policyPath, _ := cmd.Flags().GetString("file") policyPath, _ := cmd.Flags().GetString("file")
f, err := os.Open(policyPath) f, err := os.Open(policyPath)
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Error opening the policy file: %s", err), output) ErrorOutput(err, fmt.Sprintf("Error opening the policy file: %s", err), output)
return
} }
defer f.Close() defer f.Close()
policyBytes, err := io.ReadAll(f) policyBytes, err := io.ReadAll(f)
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output) ErrorOutput(err, fmt.Sprintf("Error reading the policy file: %s", err), output)
return
} }
_, err = policy.NewPolicyManager(policyBytes, nil, views.Slice[types.NodeView]{}) _, err = policy.NewPolicyManager(policyBytes, nil, views.Slice[types.NodeView]{})
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Error parsing the policy file: %s", err), output) ErrorOutput(err, fmt.Sprintf("Error parsing the policy file: %s", err), output)
return
} }
SuccessOutput(nil, "Policy is valid", "") SuccessOutput(nil, "Policy is valid", "")

View File

@ -15,16 +15,10 @@ import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
func init() { func init() {
rootCmd.AddCommand(preauthkeysCmd) rootCmd.AddCommand(preauthkeysCmd)
preauthkeysCmd.PersistentFlags().Uint64P("user", "u", 0, "User identifier (ID)") preauthkeysCmd.PersistentFlags().Uint64P("user", "u", 0, "User identifier (ID)")
preauthkeysCmd.PersistentFlags().StringP("namespace", "n", "", "User")
pakNamespaceFlag := preauthkeysCmd.PersistentFlags().Lookup("namespace")
pakNamespaceFlag.Deprecated = deprecateNamespaceMessage
pakNamespaceFlag.Hidden = true
err := preauthkeysCmd.MarkPersistentFlagRequired("user") err := preauthkeysCmd.MarkPersistentFlagRequired("user")
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("") log.Fatal().Err(err).Msg("")
@ -53,7 +47,7 @@ var listPreAuthKeys = &cobra.Command{
Short: "List the preauthkeys for this user", Short: "List the preauthkeys for this user",
Aliases: []string{"ls", "show"}, Aliases: []string{"ls", "show"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
user, err := cmd.Flags().GetUint64("user") user, err := cmd.Flags().GetUint64("user")
if err != nil { if err != nil {
@ -130,7 +124,6 @@ var listPreAuthKeys = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -142,7 +135,7 @@ var createPreAuthKeyCmd = &cobra.Command{
Short: "Creates a new preauthkey in the specified user", Short: "Creates a new preauthkey in the specified user",
Aliases: []string{"c", "new"}, Aliases: []string{"c", "new"},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
user, err := cmd.Flags().GetUint64("user") user, err := cmd.Flags().GetUint64("user")
if err != nil { if err != nil {
@ -195,7 +188,6 @@ var createPreAuthKeyCmd = &cobra.Command{
SuccessOutput(response.GetPreAuthKey(), response.GetPreAuthKey().GetKey(), output) SuccessOutput(response.GetPreAuthKey(), response.GetPreAuthKey().GetKey(), output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -214,7 +206,7 @@ var expirePreAuthKeyCmd = &cobra.Command{
return nil return nil
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
user, err := cmd.Flags().GetUint64("user") user, err := cmd.Flags().GetUint64("user")
if err != nil { if err != nil {
ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output) ErrorOutput(err, fmt.Sprintf("Error getting user: %s", err), output)
@ -240,7 +232,6 @@ var expirePreAuthKeyCmd = &cobra.Command{
SuccessOutput(response, "Key expired", output) SuccessOutput(response, "Key expired", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }

View File

@ -14,7 +14,6 @@ import (
"github.com/tcnksm/go-latest" "github.com/tcnksm/go-latest"
) )
var cfgFile string = "" var cfgFile string = ""
func init() { func init() {

View File

@ -8,10 +8,9 @@ import (
) )
const ( const (
deprecateNamespaceMessage = "use --user" HeadscaleDateTimeFormat = "2006-01-02 15:04:05"
HeadscaleDateTimeFormat = "2006-01-02 15:04:05" DefaultAPIKeyExpiry = "90d"
DefaultAPIKeyExpiry = "90d" DefaultPreAuthKeyExpiry = "1h"
DefaultPreAuthKeyExpiry = "1h"
) )
// FilterTableColumns filters table columns based on --columns flag // FilterTableColumns filters table columns based on --columns flag

View File

@ -18,16 +18,12 @@ import (
func usernameAndIDFlag(cmd *cobra.Command) { func usernameAndIDFlag(cmd *cobra.Command) {
cmd.Flags().StringP("user", "u", "", "User identifier (ID, name, or email)") cmd.Flags().StringP("user", "u", "", "User identifier (ID, name, or email)")
cmd.Flags().Uint64P("identifier", "i", 0, "User identifier (ID) - deprecated, use --user")
identifierFlag := cmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --user"
identifierFlag.Hidden = true
cmd.Flags().StringP("name", "n", "", "Username") cmd.Flags().StringP("name", "n", "", "Username")
} }
// usernameAndIDFromFlag returns the user ID using smart lookup. // userIDFromFlag returns the user ID using smart lookup.
// If no user is specified, it will exit the program with an error. // If no user is specified, it will exit the program with an error.
func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) { func userIDFromFlag(cmd *cobra.Command) uint64 {
userID, err := GetUserIdentifier(cmd) userID, err := GetUserIdentifier(cmd)
if err != nil { if err != nil {
ErrorOutput( ErrorOutput(
@ -37,7 +33,7 @@ func usernameAndIDFromFlag(cmd *cobra.Command) (uint64, string) {
) )
} }
return userID, "" return userID
} }
func init() { func init() {
@ -52,11 +48,6 @@ func init() {
listUsersCmd.Flags().Uint64P("id", "", 0, "Filter by user ID") listUsersCmd.Flags().Uint64P("id", "", 0, "Filter by user ID")
listUsersCmd.Flags().StringP("name", "n", "", "Filter by username") listUsersCmd.Flags().StringP("name", "n", "", "Filter by username")
listUsersCmd.Flags().StringP("email", "e", "", "Filter by email address") listUsersCmd.Flags().StringP("email", "e", "", "Filter by email address")
// Backward compatibility (deprecated)
listUsersCmd.Flags().Uint64P("identifier", "i", 0, "Filter by user ID - deprecated, use --id")
identifierFlag := listUsersCmd.Flags().Lookup("identifier")
identifierFlag.Deprecated = "use --id"
identifierFlag.Hidden = true
listUsersCmd.Flags().String("columns", "", "Comma-separated list of columns to display (ID,Name,Username,Email,Created)") listUsersCmd.Flags().String("columns", "", "Comma-separated list of columns to display (ID,Name,Username,Email,Created)")
userCmd.AddCommand(destroyUserCmd) userCmd.AddCommand(destroyUserCmd)
usernameAndIDFlag(destroyUserCmd) usernameAndIDFlag(destroyUserCmd)
@ -131,7 +122,6 @@ var createUserCmd = &cobra.Command{
SuccessOutput(response.GetUser(), "User created", output) SuccessOutput(response.GetUser(), "User created", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -145,10 +135,9 @@ var destroyUserCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output := GetOutputFlag(cmd) output := GetOutputFlag(cmd)
id, username := usernameAndIDFromFlag(cmd) id := userIDFromFlag(cmd)
request := &v1.ListUsersRequest{ request := &v1.ListUsersRequest{
Name: username, Id: id,
Id: id,
} }
var user *v1.User var user *v1.User
@ -176,7 +165,6 @@ var destroyUserCmd = &cobra.Command{
user = users.GetUsers()[0] user = users.GetUsers()[0]
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -212,7 +200,6 @@ var destroyUserCmd = &cobra.Command{
SuccessOutput(response, "User destroyed", output) SuccessOutput(response, "User destroyed", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }
@ -247,8 +234,6 @@ var listUsersCmd = &cobra.Command{
// Check specific filter flags // Check specific filter flags
if id, _ := cmd.Flags().GetUint64("id"); id > 0 { if id, _ := cmd.Flags().GetUint64("id"); id > 0 {
request.Id = id request.Id = id
} else if identifier, _ := cmd.Flags().GetUint64("identifier"); identifier > 0 {
request.Id = identifier // backward compatibility
} else if name, _ := cmd.Flags().GetString("name"); name != "" { } else if name, _ := cmd.Flags().GetString("name"); name != "" {
request.Name = name request.Name = name
} else if email, _ := cmd.Flags().GetString("email"); email != "" { } else if email, _ := cmd.Flags().GetString("email"); email != "" {
@ -296,7 +281,6 @@ var listUsersCmd = &cobra.Command{
} }
return nil return nil
}) })
if err != nil { if err != nil {
// Error already handled in closure // Error already handled in closure
return return
@ -311,13 +295,12 @@ var renameUserCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output := GetOutputFlag(cmd) output := GetOutputFlag(cmd)
id, username := usernameAndIDFromFlag(cmd) id := userIDFromFlag(cmd)
newName, _ := cmd.Flags().GetString("new-name") newName, _ := cmd.Flags().GetString("new-name")
err := WithClient(func(ctx context.Context, client v1.HeadscaleServiceClient) error { err := WithClient(func(ctx context.Context, client v1.HeadscaleServiceClient) error {
listReq := &v1.ListUsersRequest{ listReq := &v1.ListUsersRequest{
Name: username, Id: id,
Id: id,
} }
users, err := client.ListUsers(ctx, listReq) users, err := client.ListUsers(ctx, listReq)
@ -358,7 +341,6 @@ var renameUserCmd = &cobra.Command{
SuccessOutput(response.GetUser(), "User renamed", output) SuccessOutput(response.GetUser(), "User renamed", output)
return nil return nil
}) })
if err != nil { if err != nil {
return return
} }

View File

@ -22,10 +22,6 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
const (
SocketWritePermissions = 0o666
)
func newHeadscaleServerWithConfig() (*hscontrol.Headscale, error) { func newHeadscaleServerWithConfig() (*hscontrol.Headscale, error) {
cfg, err := types.LoadServerConfig() cfg, err := types.LoadServerConfig()
if err != nil { if err != nil {
@ -75,7 +71,7 @@ func newHeadscaleCLIWithConfig() (context.Context, v1.HeadscaleServiceClient, *g
// Try to give the user better feedback if we cannot write to the headscale // Try to give the user better feedback if we cannot write to the headscale
// socket. // socket.
socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, SocketWritePermissions) // nolint socket, err := os.OpenFile(cfg.UnixSocket, os.O_WRONLY, 0o666) // nolint
if err != nil { if err != nil {
if os.IsPermission(err) { if os.IsPermission(err) {
log.Fatal(). log.Fatal().
@ -210,15 +206,10 @@ func GetOutputFlag(cmd *cobra.Command) string {
return output return output
} }
// GetNodeIdentifier returns the node ID using smart lookup via gRPC ListNodes call // GetNodeIdentifier returns the node ID using smart lookup via gRPC ListNodes call
func GetNodeIdentifier(cmd *cobra.Command) (uint64, error) { func GetNodeIdentifier(cmd *cobra.Command) (uint64, error) {
nodeFlag, _ := cmd.Flags().GetString("node") nodeFlag, _ := cmd.Flags().GetString("node")
identifierFlag, _ := cmd.Flags().GetUint64("identifier")
// Check if --identifier (deprecated) was used
if identifierFlag > 0 {
return identifierFlag, nil
}
// Use --node flag // Use --node flag
if nodeFlag == "" { if nodeFlag == "" {
@ -270,7 +261,6 @@ func lookupNodeBySpecifier(specifier string) (uint64, error) {
nodeID = nodes[0].GetId() nodeID = nodes[0].GetId()
return nil return nil
}) })
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -295,7 +285,6 @@ func isIPAddress(s string) bool {
func GetUserIdentifier(cmd *cobra.Command) (uint64, error) { func GetUserIdentifier(cmd *cobra.Command) (uint64, error) {
userFlag, _ := cmd.Flags().GetString("user") userFlag, _ := cmd.Flags().GetString("user")
nameFlag, _ := cmd.Flags().GetString("name") nameFlag, _ := cmd.Flags().GetString("name")
identifierFlag, _ := cmd.Flags().GetUint64("identifier")
var specifier string var specifier string
@ -304,8 +293,6 @@ func GetUserIdentifier(cmd *cobra.Command) (uint64, error) {
specifier = userFlag specifier = userFlag
} else if nameFlag != "" { } else if nameFlag != "" {
specifier = nameFlag specifier = nameFlag
} else if identifierFlag > 0 {
return identifierFlag, nil // Direct ID, no lookup needed
} else { } else {
return 0, fmt.Errorf("--user flag is required") return 0, fmt.Errorf("--user flag is required")
} }
@ -355,7 +342,6 @@ func lookupUserBySpecifier(specifier string) (uint64, error) {
userID = users[0].GetId() userID = users[0].GetId()
return nil return nil
}) })
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -14,7 +14,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version", Short: "Print the version",
Long: "The version of headscale", Long: "The version of headscale",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
output, _ := cmd.Flags().GetString("output") output := GetOutputFlag(cmd)
SuccessOutput(map[string]string{ SuccessOutput(map[string]string{
"version": types.Version, "version": types.Version,
"commit": types.GitCommitHash, "commit": types.GitCommitHash,

2
go.mod
View File

@ -81,7 +81,7 @@ require (
modernc.org/libc v1.62.1 // indirect modernc.org/libc v1.62.1 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.10.0 // indirect modernc.org/memory v1.10.0 // indirect
modernc.org/sqlite v1.37.0 // indirect modernc.org/sqlite v1.37.0
) )
require ( require (

View File

@ -4,6 +4,7 @@ import (
"cmp" "cmp"
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices"
"strconv" "strconv"
"strings" "strings"
"testing" "testing"
@ -18,7 +19,6 @@ import (
"github.com/juanfont/headscale/integration/tsic" "github.com/juanfont/headscale/integration/tsic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/slices"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
) )
@ -95,7 +95,7 @@ func TestUserCommand(t *testing.T) {
"users", "users",
"rename", "rename",
"--output=json", "--output=json",
fmt.Sprintf("--identifier=%d", listUsers[1].GetId()), fmt.Sprintf("--user=%d", listUsers[1].GetId()),
"--new-name=newname", "--new-name=newname",
}, },
) )
@ -161,7 +161,7 @@ func TestUserCommand(t *testing.T) {
"list", "list",
"--output", "--output",
"json", "json",
"--identifier=1", "--user=1",
}, },
&listByID, &listByID,
) )
@ -187,7 +187,7 @@ func TestUserCommand(t *testing.T) {
"destroy", "destroy",
"--force", "--force",
// Delete "user1" // Delete "user1"
"--identifier=1", "--user=1",
}, },
) )
assert.NoError(t, err) assert.NoError(t, err)
@ -354,7 +354,10 @@ func TestPreAuthKeyCommand(t *testing.T) {
continue continue
} }
assert.Equal(t, []string{"tag:test1", "tag:test2"}, listedPreAuthKeys[index].GetAclTags()) // Sort tags for consistent comparison
tags := listedPreAuthKeys[index].GetAclTags()
slices.Sort(tags)
assert.Equal(t, []string{"tag:test1", "tag:test2"}, tags)
} }
// Test key expiry // Test key expiry
@ -869,7 +872,7 @@ func TestNodeTagCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"tag", "tag",
"-i", "1", "--node", "1",
"-t", "tag:test", "-t", "tag:test",
"--output", "json", "--output", "json",
}, },
@ -884,7 +887,7 @@ func TestNodeTagCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"tag", "tag",
"-i", "2", "--node", "2",
"-t", "wrong-tag", "-t", "wrong-tag",
"--output", "json", "--output", "json",
}, },
@ -1259,7 +1262,7 @@ func TestNodeCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"delete", "delete",
"--identifier", "--node",
// Delete the last added machine // Delete the last added machine
"4", "4",
"--output", "--output",
@ -1385,7 +1388,7 @@ func TestNodeExpireCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"expire", "expire",
"--identifier", "--node",
strconv.FormatUint(listAll[idx].GetId(), 10), strconv.FormatUint(listAll[idx].GetId(), 10),
}, },
) )
@ -1511,7 +1514,7 @@ func TestNodeRenameCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"rename", "rename",
"--identifier", "--node",
strconv.FormatUint(listAll[idx].GetId(), 10), strconv.FormatUint(listAll[idx].GetId(), 10),
fmt.Sprintf("newnode-%d", idx+1), fmt.Sprintf("newnode-%d", idx+1),
}, },
@ -1549,7 +1552,7 @@ func TestNodeRenameCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"rename", "rename",
"--identifier", "--node",
strconv.FormatUint(listAll[4].GetId(), 10), strconv.FormatUint(listAll[4].GetId(), 10),
strings.Repeat("t", 64), strings.Repeat("t", 64),
}, },
@ -1649,7 +1652,7 @@ func TestNodeMoveCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"move", "move",
"--identifier", "--node",
strconv.FormatUint(node.GetId(), 10), strconv.FormatUint(node.GetId(), 10),
"--user", "--user",
strconv.FormatUint(userMap["new-user"].GetId(), 10), strconv.FormatUint(userMap["new-user"].GetId(), 10),
@ -1687,7 +1690,7 @@ func TestNodeMoveCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"move", "move",
"--identifier", "--node",
nodeID, nodeID,
"--user", "--user",
"999", "999",
@ -1708,7 +1711,7 @@ func TestNodeMoveCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"move", "move",
"--identifier", "--node",
nodeID, nodeID,
"--user", "--user",
strconv.FormatUint(userMap["old-user"].GetId(), 10), strconv.FormatUint(userMap["old-user"].GetId(), 10),
@ -1727,7 +1730,7 @@ func TestNodeMoveCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"move", "move",
"--identifier", "--node",
nodeID, nodeID,
"--user", "--user",
strconv.FormatUint(userMap["old-user"].GetId(), 10), strconv.FormatUint(userMap["old-user"].GetId(), 10),

View File

@ -41,7 +41,7 @@ func TestDebugCommand(t *testing.T) {
// Help text should contain expected information // Help text should contain expected information
assert.Contains(t, result, "debug", "help should mention debug command") assert.Contains(t, result, "debug", "help should mention debug command")
assert.Contains(t, result, "debug and testing commands", "help should contain command description") assert.Contains(t, result, "debugging and testing", "help should contain command description")
assert.Contains(t, result, "create-node", "help should mention create-node subcommand") assert.Contains(t, result, "create-node", "help should mention create-node subcommand")
}) })

View File

@ -564,10 +564,10 @@ func TestUpdateHostnameFromClient(t *testing.T) {
_, err = headscale.Execute( _, err = headscale.Execute(
[]string{ []string{
"headscale", "headscale",
"node", "nodes",
"rename", "rename",
givenName, givenName,
"--identifier", "--node",
strconv.FormatUint(node.GetId(), 10), strconv.FormatUint(node.GetId(), 10),
}) })
assertNoErr(t, err) assertNoErr(t, err)
@ -702,7 +702,7 @@ func TestExpireNode(t *testing.T) {
// TODO(kradalby): This is Headscale specific and would not play nicely // TODO(kradalby): This is Headscale specific and would not play nicely
// with other implementations of the ControlServer interface // with other implementations of the ControlServer interface
result, err := headscale.Execute([]string{ result, err := headscale.Execute([]string{
"headscale", "nodes", "expire", "--identifier", "1", "--output", "json", "headscale", "nodes", "expire", "--node", "1", "--output", "json",
}) })
assertNoErr(t, err) assertNoErr(t, err)
@ -1060,7 +1060,7 @@ func Test2118DeletingOnlineNodePanics(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"delete", "delete",
"--identifier", "--node",
// Delete the last added machine // Delete the last added machine
fmt.Sprintf("%d", nodeList[0].GetId()), fmt.Sprintf("%d", nodeList[0].GetId()),
"--output", "--output",

View File

@ -320,7 +320,9 @@ func TestGenerateCommandEdgeCases(t *testing.T) {
// Should fail gracefully for non-existent subcommand // Should fail gracefully for non-existent subcommand
assert.Error(t, err, "should fail for non-existent subcommand") assert.Error(t, err, "should fail for non-existent subcommand")
assert.NotContains(t, err.Error(), "panic", "should not panic on non-existent subcommand") if err != nil {
assert.NotContains(t, err.Error(), "panic", "should not panic on non-existent subcommand")
}
}) })
t.Run("test_generate_key_format_consistency", func(t *testing.T) { t.Run("test_generate_key_format_consistency", func(t *testing.T) {
@ -349,8 +351,8 @@ func TestGenerateCommandEdgeCases(t *testing.T) {
for _, char := range keyPart { for _, char := range keyPart {
assert.True(t, assert.True(t,
(char >= '0' && char <= '9') || (char >= '0' && char <= '9') ||
(char >= 'a' && char <= 'f') || (char >= 'a' && char <= 'f') ||
(char >= 'A' && char <= 'F'), (char >= 'A' && char <= 'F'),
"private key should only contain hex characters, found: %c", char) "private key should only contain hex characters, found: %c", char)
} }
}) })

View File

@ -1122,7 +1122,7 @@ func (t *HeadscaleInContainer) ApproveRoutes(id uint64, routes []netip.Prefix) (
command := []string{ command := []string{
"headscale", "nodes", "approve-routes", "headscale", "nodes", "approve-routes",
"--output", "json", "--output", "json",
"--identifier", strconv.FormatUint(id, 10), "--node", strconv.FormatUint(id, 10),
"--routes=" + strings.Join(util.PrefixesToString(routes), ","), "--routes=" + strings.Join(util.PrefixesToString(routes), ","),
} }

View File

@ -112,7 +112,7 @@ func TestRouteCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"list-routes", "list-routes",
"--identifier", "--node",
fmt.Sprintf("%d", nodeID), fmt.Sprintf("%d", nodeID),
}, },
) )
@ -124,7 +124,7 @@ func TestRouteCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"approve-routes", "approve-routes",
"--identifier", "--node",
fmt.Sprintf("%d", nodeID), fmt.Sprintf("%d", nodeID),
"--routes", "--routes",
"10.0.0.0/24", "10.0.0.0/24",
@ -158,7 +158,7 @@ func TestRouteCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"approve-routes", "approve-routes",
"--identifier", "--node",
fmt.Sprintf("%d", nodeID), fmt.Sprintf("%d", nodeID),
"--routes", "--routes",
"", // Empty string removes all routes "", // Empty string removes all routes
@ -192,7 +192,7 @@ func TestRouteCommand(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"list-routes", "list-routes",
"--identifier", "--node",
fmt.Sprintf("%d", nodeID), fmt.Sprintf("%d", nodeID),
"--output", "--output",
"json", "json",
@ -231,7 +231,7 @@ func TestRouteCommandEdgeCases(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"list-routes", "list-routes",
"--identifier", "--node",
"999999", "999999",
}, },
) )
@ -246,7 +246,7 @@ func TestRouteCommandEdgeCases(t *testing.T) {
"headscale", "headscale",
"nodes", "nodes",
"approve-routes", "approve-routes",
"--identifier", "--node",
"1", "1",
"--routes", "--routes",
"invalid-cidr", "invalid-cidr",
@ -287,7 +287,7 @@ func TestRouteCommandHelp(t *testing.T) {
// Verify help text contains expected information // Verify help text contains expected information
assert.Contains(t, result, "list-routes", "help should mention list-routes command") assert.Contains(t, result, "list-routes", "help should mention list-routes command")
assert.Contains(t, result, "identifier", "help should mention identifier flag") assert.Contains(t, result, "node", "help should mention node flag")
}) })
t.Run("test_approve_routes_help", func(t *testing.T) { t.Run("test_approve_routes_help", func(t *testing.T) {
@ -303,7 +303,7 @@ func TestRouteCommandHelp(t *testing.T) {
// Verify help text contains expected information // Verify help text contains expected information
assert.Contains(t, result, "approve-routes", "help should mention approve-routes command") assert.Contains(t, result, "approve-routes", "help should mention approve-routes command")
assert.Contains(t, result, "identifier", "help should mention identifier flag") assert.Contains(t, result, "node", "help should mention node flag")
assert.Contains(t, result, "routes", "help should mention routes flag") assert.Contains(t, result, "routes", "help should mention routes flag")
}) })
} }