1
0
mirror of https://github.com/juanfont/headscale.git synced 2026-02-07 20:04:00 +01:00
juanfont.headscale/cmd/headscale/cli/SIMPLIFICATION.md
Kristoffer Dalby 67f2c20052 compli
2025-07-14 20:43:57 +00:00

2.1 KiB

CLI Simplification - WithClient Pattern

Problem

Every CLI command has repetitive gRPC client setup boilerplate:

// This pattern appears 25+ times across all commands
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()
defer conn.Close()

// ... command logic ...

Solution

Simple closure that handles client lifecycle:

// client.go - 16 lines total
func WithClient(fn func(context.Context, v1.HeadscaleServiceClient) error) error {
	ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
	defer cancel()
	defer conn.Close()
	
	return fn(ctx, client)
}

Usage Example

Before (users.go listUsersCmd):

Run: func(cmd *cobra.Command, args []string) {
    output, _ := cmd.Flags().GetString("output")
    
    ctx, client, conn, cancel := newHeadscaleCLIWithConfig()  // 4 lines
    defer cancel()
    defer conn.Close()
    
    request := &v1.ListUsersRequest{}
    // ... build request ...
    
    response, err := client.ListUsers(ctx, request)
    if err != nil {
        ErrorOutput(err, "Cannot get users: "+status.Convert(err).Message(), output)
    }
    // ... handle response ...
}

After:

Run: func(cmd *cobra.Command, args []string) {
    output, _ := cmd.Flags().GetString("output")
    
    err := WithClient(func(ctx context.Context, client v1.HeadscaleServiceClient) error {
        request := &v1.ListUsersRequest{}
        // ... build request ...
        
        response, err := client.ListUsers(ctx, request)
        if err != nil {
            ErrorOutput(err, "Cannot get users: "+status.Convert(err).Message(), output)
            return err
        }
        // ... handle response ...
        return nil
    })
    
    if err != nil {
        return  // Error already handled
    }
}

Benefits

  • Removes 4 lines of boilerplate from every command
  • Ensures proper cleanup - no forgetting defer statements
  • Simpler error handling - return from closure, handled centrally
  • Easy to apply - minimal changes to existing commands

Rollout

This pattern can be applied to all 25+ commands systematically, removing ~100 lines of repetitive boilerplate.