-
-
Notifications
You must be signed in to change notification settings - Fork 0
Replace promptui with huh #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughReplaced promptui/fuzzyfinder UIs with huh-based forms, made prompts context-aware, added Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant CLI as tailout CLI
participant HUH as huh UI
participant API as tsapi.Client
Note right of CLI #f6f8ff: Exit-node update (context-aware)
User->>CLI: choose connect / disconnect
CLI->>HUH: present node selection / confirm
HUH-->>CLI: selected node or cancel
CLI->>API: UpdateExitNode(ctx, nodeID)
API-->>CLI: success / error
CLI-->>User: "Connected to node..." / "Disconnected from exit node."
sequenceDiagram
autonumber
participant User
participant CLI as tailout Create
participant HUH as huh UI
participant AWS as EC2/SSM
Note right of CLI #f6f8ff: Staged instance creation (prepare → create → install)
User->>CLI: trigger create (prompts)
CLI->>HUH: AMI and params selection
HUH-->>CLI: confirmed inputs
CLI->>AWS: RunInstances (createInstance)
AWS-->>CLI: instanceId / metadata
CLI->>AWS: Waiter & Tag instance
CLI->>AWS: SSM SendCommand (installTailScale)
AWS-->>CLI: command result
CLI-->>User: node join info / errors
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
internal/common.go (1)
111-116: Fix recency check; current logic is wrong.
device.LastSeen.Minute()returns the minute within the hour (0–59), not “minutes ago”. This admits stale nodes and excludes fresh ones around hour boundaries.Apply this diff:
- if time.Duration(device.LastSeen.Minute()) < 10*time.Minute { + if time.Since(device.LastSeen) < 10*time.Minute { tailoutDevices = append(tailoutDevices, device) }
🧹 Nitpick comments (7)
go.mod (2)
88-88: Use the canonical YAML module path.
go.yaml.in/yaml/v3exists, but the canonical and widely used path isgopkg.in/yaml.v3. Prefer the canonical path to avoid tooling surprises.Apply this diff:
- go.yaml.in/yaml/v3 v3.0.4 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirectReference: official import path is
gopkg.in/yaml.v3. (github.com)
15-15: Optional: avoid long‑lived pseudo‑versions for tools/UI libs.If stability matters, consider pinning
github.com/charmbracelet/huh/spinnerto a tagged release once available to reduce churn from pseudo‑versions. This is optional.internal/common.go (2)
53-55: Remove duplicate sorting.
GetRegionsalready sortsregionNames. The extra sort inSelectRegionis redundant.Apply this diff:
- sort.Slice(regionNames, func(i, j int) bool { - return regionNames[i] < regionNames[j] - })Also applies to: 36-39
69-77: Handle cancellation robustly; avoid string‑matching “^C”.Compare with
context.Canceled(orctx.Err()) instead of matching error text.Apply this diff:
- select { - case <-ctx.Done(): - return "", ctx.Err() - default: - if err.Error() == "^C" { - return "", context.Canceled - } - return "", fmt.Errorf("failed to select region: %w", err) - } + if errors.Is(err, context.Canceled) || ctx.Err() != nil { + return "", context.Canceled + } + return "", fmt.Errorf("failed to select region: %w", err)tailout/connect.go (1)
49-51: Optional: auto‑select when only one node is available.If exactly one active node exists and interactive mode is enabled, select it without prompting for a smoother UX.
tailout/create.go (2)
291-293: Avoid returning(nil, nil)— an anti-pattern in Go.When the user declines instance creation, the function returns
(nil, nil). This requires the caller to check both the return value and error, which is error-prone. Instead, return a sentinel error or a custom error type to clearly indicate user cancellation.Define a sentinel error at the package level:
var ErrUserCancelled = errors.New("user cancelled operation")Then apply this diff:
if !result { - return nil, nil + return nil, ErrUserCancelled }Update the caller (lines 102-109) to check for this error:
runInput, errPrep := prepareInstance(ctx, cfg, aws.Bool(dryRun)) if errPrep != nil { + if errors.Is(errPrep, ErrUserCancelled) { + fmt.Println("Instance creation cancelled.") + return nil + } return fmt.Errorf("failed to prepare instance: %w", errPrep) } - if runInput == nil { - fmt.Println("Instance creation aborted.") - return nil - }
363-370: Use a more descriptive variable name.The variable
instance1is confusing. Consider renaming it todescribedInstanceorrunningInstanceto clarify that it's the instance retrieved from the DescribeInstances call.Apply this diff:
- instance1 := reservation.Instances[0] - if instance1.PublicIpAddress == nil { + describedInstance := reservation.Instances[0] + if describedInstance.PublicIpAddress == nil { return instance, errors.New("no public IP address found") } instance.Name = nodeName instance.InstanceID = *createdInstance.InstanceId - instance.IP = *instance1.PublicIpAddress + instance.IP = *describedInstance.PublicIpAddress
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.suminternal/assets/spinner.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
Makefile(1 hunks)go.mod(5 hunks)internal/common.go(3 hunks)tailout/connect.go(2 hunks)tailout/create.go(7 hunks)tailout/disconnect.go(1 hunks)tailout/init.go(1 hunks)tailout/stop.go(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
tailout/connect.go (1)
internal/common.go (1)
UpdateExitNode(123-186)
tailout/stop.go (1)
internal/common.go (1)
PromptYesNo(84-101)
tailout/init.go (1)
internal/common.go (1)
PromptYesNo(84-101)
tailout/create.go (2)
tailout/config/config.go (1)
Config(13-21)internal/common.go (1)
PromptYesNo(84-101)
tailout/disconnect.go (3)
tailout/app.go (1)
App(7-9)tailout/config/config.go (1)
Config(13-21)internal/common.go (1)
UpdateExitNode(123-186)
🪛 checkmake (0.2.2)
Makefile
[warning] 7-7: Missing required phony target "all"
(minphony)
[warning] 7-7: Missing required phony target "clean"
(minphony)
[warning] 7-7: Missing required phony target "test"
(minphony)
🔇 Additional comments (15)
Makefile (1)
7-9: Newtailoutbuild target looks good.The target is properly declared as
.PHONYand the goreleaser flags (--snapshot,--clean,--single-target) are appropriate for building a local development snapshot.go.mod (1)
5-5: Tool directive looks good; ensure CI uses Go ≥ 1.24 (you’re on 1.25.3).The
tooldirective is supported starting with Go 1.24, and withgo 1.25.3you’re covered. Just confirm CI/build images aren’t on older toolchains that would choke ontool. (tip.golang.org)tailout/disconnect.go (1)
13-21: LGTM: consistent API‑based disconnect.Clean switch to the shared
UpdateExitNodeflow; messages and errors read well.Also applies to: 23-29
tailout/init.go (1)
108-108: LGTM: prompt is now context‑aware.All
PromptYesNocall sites across the codebase (tailout/stop.go,tailout/create.go,tailout/init.go) properly passctxas the first parameter, confirming consistent context propagation.tailout/stop.go (4)
14-14: LGTM! Clean migration to huh.The import addition supports the new multi-select UI implementation.
48-83: LGTM! Well-structured multi-select implementation.The huh-based selection flow is clean and handles edge cases properly:
- Meaningful labels with hostname and IP address
- Proper error handling for selection failures
- Early return on zero selections
- Correct mapping from indices to nodes
98-101: LGTM! Good defensive check.The early return for empty nodesToStop prevents unnecessary processing and provides clear user feedback.
109-109: LGTM! Correct context propagation.The updated call aligns with the new context-aware signature of
internal.PromptYesNo.tailout/create.go (7)
17-22: LGTM! Necessary imports for new functionality.The new imports support SSM-based Tailscale installation, dry-run error handling, and spinner UI feedback.
97-97: LGTM! Good addition of retry configuration.The retry configuration improves resilience for AWS API calls, especially helpful with spot instance operations.
102-175: LGTM! Well-structured staged provisioning flow.The refactored approach provides:
- Clear separation of concerns (prepare, create, install)
- Better UX with spinner feedback
- Proper error handling at each stage
- Verification that the node joined the tailnet
178-182: LGTM! Clean data structure.The
instancestruct provides a clear way to pass instance details between the staged functions.
303-309: LGTM! Proper dry-run error handling.The dry-run detection using Smithy's error type is correct, and returning a zero-valued instance is appropriate since the caller checks for empty
InstanceID.
337-343: LGTM! Proper use of AWS SDK waiter.The instance status waiter with a 5-minute timeout is appropriate for EC2 instance startup, and the spinner title update provides good UX feedback.
375-421: LGTM! Clean SSM-based installation.The SSM approach is robust and provides:
- Non-interactive remote installation
- Proper wait for command completion with timeout
- Status checking with detailed error output
Note: This relies on the SSM agent being pre-installed on Amazon Linux 2023 AMIs, which is the default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
tailout/connect.go (2)
54-60: Avoid panic on empty addresses; add defensive check.
device.Addresses[0]will panic if the slice is empty. Add a guard similar to the pattern used intailout/stop.golines 51-57.Apply this diff:
// Create options for huh select options := make([]huh.Option[int], len(tailoutDevices)) for i, device := range tailoutDevices { - // Display hostname and IP address - label := fmt.Sprintf("%s (%s)", device.Hostname, device.Addresses[0]) + addr := "no IP" + if len(device.Addresses) > 0 { + addr = device.Addresses[0] + } + label := fmt.Sprintf("%s (%s)", device.Hostname, addr) options[i] = huh.NewOption(label, i) }
88-88: Avoid panic on empty addresses in success message.Same issue as lines 54-60:
deviceToConnectTo.Addresses[0]can panic if Addresses is empty.Apply this diff:
+ addr := "no IP" + if len(deviceToConnectTo.Addresses) > 0 { + addr = deviceToConnectTo.Addresses[0] + } - fmt.Printf("Connected to node %s (%s) via Tailscale.\n", deviceToConnectTo.Hostname, deviceToConnectTo.Addresses[0]) + fmt.Printf("Connected to node %s (%s) via Tailscale.\n", deviceToConnectTo.Hostname, addr)tailout/create.go (2)
243-243: Use the calculateddurationMinutesvariable instead of hardcoded value.The shutdown time is hardcoded as 10 minutes, but the
durationMinutesvariable (calculated from the user-providedshutdownconfig at lines 84-87) is never used.Apply this diff:
-sudo echo "sudo shutdown" | at now + ` + strconv.Itoa(10) + ` minutes` +sudo echo "sudo shutdown" | at now + ` + strconv.Itoa(durationMinutes) + ` minutes`Note: You'll need to pass
durationMinutesas a parameter toprepareInstanceto access it here.
290-290: Display the actual shutdown duration instead of "TODO".The user-facing output shows "TODO" for the auto shutdown time. Pass
durationMinutesas a parameter toprepareInstanceto display the actual value.Apply this diff:
- Auto shutdown after: %s - `, *identity.Account, imageID, imageName, imageOwner, imageArchitecture, types.InstanceTypeT3aMicro, cfg.Region, "TODO") + `, *identity.Account, imageID, imageName, imageOwner, imageArchitecture, types.InstanceTypeT3aMicro, cfg.Region, strconv.Itoa(durationMinutes) + " minutes")And update the function signature:
-func prepareInstance(ctx context.Context, cfg aws.Config, dryRun *bool) (instance *ec2.RunInstancesInput, err error) { +func prepareInstance(ctx context.Context, cfg aws.Config, dryRun *bool, durationMinutes int) (instance *ec2.RunInstancesInput, err error) {Then update the call at line 104:
- runInput, errPrep := prepareInstance(ctx, cfg, aws.Bool(dryRun)) + runInput, errPrep := prepareInstance(ctx, cfg, aws.Bool(dryRun), durationMinutes)
🧹 Nitpick comments (2)
internal/common.go (2)
53-55: Remove redundant sort.
GetRegionsalready sorts the region names at lines 36-38, so this sort is unnecessary.Apply this diff:
- sort.Slice(regionNames, func(i, j int) bool { - return regionNames[i] < regionNames[j] - }) - var selectedRegion string
90-93: Add context cancellation check for consistency.For consistency with
SelectRegion(lines 69-71), check for context cancellation explicitly.Apply this diff:
err := form.RunWithContext(ctx) if err != nil { + if ctxErr := ctx.Err(); ctxErr != nil { + return false, fmt.Errorf("prompt canceled: %w", ctxErr) + } return false, fmt.Errorf("failed to prompt for yes/no: %w", err) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
internal/common.go(3 hunks)tailout/connect.go(2 hunks)tailout/create.go(7 hunks)tailout/stop.go(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
tailout/create.go (2)
tailout/config/config.go (1)
Config(13-21)internal/common.go (1)
PromptYesNo(79-96)
tailout/connect.go (1)
internal/common.go (1)
UpdateExitNode(118-181)
tailout/stop.go (1)
internal/common.go (1)
PromptYesNo(79-96)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: binary
🔇 Additional comments (2)
tailout/stop.go (1)
48-83: Excellent defensive pattern for handling addresses.The guard at lines 51-54 properly prevents panics when
Addressesis empty. This is the correct pattern that should be replicated intailout/connect.go(see my comments there).tailout/create.go (1)
292-299: LGTM! Correctly integrated with updatedPromptYesNoAPI.The context is properly passed to
internal.PromptYesNo, and the user abort case returns the custom error for clear error handling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
tailout/create.go (3)
138-139: Clarify or remove the dry run comment.The comment "// If dry run, exit here?" is unclear because dry run is already handled within
createInstanceat lines 310-313. If additional dry run logic is needed here, implement it; otherwise, remove the comment.Apply this diff if no additional logic is needed:
- // If dry run, exit here? -
282-290: Consider clarifying the shutdown duration unit in the display.The display shows
shutdownDurationbut doesn't explicitly state the unit. Consider adding " minutes" to line 288 for clarity.Apply this diff:
-- Auto shutdown after: %s +- Auto shutdown after: %s minutes
344-346: Consider increasing the instance status wait timeout.The 5-minute timeout for
InstanceStatusOkWaitermight be insufficient for spot instances, which can take longer to reach running status, especially during high demand periods. Consider increasing to 10 minutes.Apply this diff:
- }, time.Minute*5) + }, time.Minute*10)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
internal/common.go(3 hunks)tailout/create.go(7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tailout/create.go (2)
tailout/config/config.go (1)
Config(13-21)internal/common.go (1)
PromptYesNo(75-95)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: binary
🔇 Additional comments (12)
internal/common.go (3)
36-38: LGTM: Alphabetical sorting improves UX.The alphabetical sorting of region names enhances user experience by making regions easier to locate.
43-72: LGTM: Context-aware region selection with proper cancellation handling.The migration to
huhis implemented correctly with proper context cancellation handling throughout.
74-95: LGTM: Context-aware confirmation prompt.The function correctly implements context-aware confirmation using
huhwith proper cancellation handling.tailout/create.go (9)
27-27: LGTM: Error sentinel for user-initiated aborts.The
ErrUserAbortedsentinel is a good practice for distinguishing user-initiated cancellations from actual errors.
99-99: LGTM: Retry configuration improves resilience.Adding retry configuration with 5 max attempts and standard mode improves the resilience of AWS SDK operations.
104-115: LGTM: Proper handling of ErrUserAborted and nil returns.The handling of
ErrUserAbortedand nil returns fromprepareInstancecorrectly distinguishes user-initiated aborts from errors.
216-216: LGTM: Explicit nil returns as suggested in past review.The function now explicitly returns
nilon error paths instead of relying on uninitialized named return variables, improving code clarity.Also applies to: 220-220, 279-279
243-243: LGTM: Shutdown duration parameter is now used correctly.The
shutdownDurationparameter is correctly used in the user data script, addressing the past review comment about using the calculated duration instead of a hardcoded value.
255-266: LGTM: Tagging resources at creation time.Adding the
Apptag inTagSpecificationsensures the instance is properly tagged from creation, which is a best practice for resource management.
309-314: LGTM: Proper dry run handling.The dry run operation is correctly detected and handled, with an appropriate message and early return.
184-188: LGTM: Clean data structure for instance details.The
instancestruct provides a clean way to pass instance details between the staged provisioning functions.
381-427: LGTM: SSM-based installation with proper error handling.The SSM-based TailScale installation is well-structured with proper waiting and status validation. The error handling correctly includes stderr output on failure.
However, the 5-minute timeout at line 409 might be insufficient for instances with slow network connections or during package manager updates. Consider increasing to 10 minutes:
- }, 5*time.Minute) + }, 10*time.Minute)You can verify typical SSM command execution times in your environment by checking CloudWatch Logs for SSM Run Command executions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
tailout/connect.go (1)
93-98: Previous issue resolved: defensive address handling in success message.Lines 94-97 apply the same safe pattern as the interactive flow, checking array length before accessing
deviceToConnectTo.Addresses[0]. This completes the mitigation of the panic risk flagged in earlier reviews.Optional: The address fallback logic appears twice (lines 58-61 and here). Extracting it into a helper function like
getDeviceAddress(device tsapi.Device) stringwould reduce duplication, but given the minimal size (3 lines), this is not essential.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
tailout/connect.go(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tailout/connect.go (1)
internal/common.go (1)
UpdateExitNode(117-180)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: binary
🔇 Additional comments (4)
tailout/connect.go (4)
10-10: LGTM! Clean transition to huh.The import of
huhreplaces the deprecatedpromptuidependency, aligning with the PR objectives.
44-48: Previous issue resolved: NodeID now correctly extracted.The bug flagged in earlier reviews (passing hostname to
UpdateExitNode) has been fixed. Line 48 now correctly assignsdeviceToConnectTo.NodeIDtonodeConnect, ensuring the function receives a NodeID rather than a hostname.
54-83: Previous issue resolved: defensive address handling added.The panic risk flagged in earlier reviews has been mitigated. Lines 58-61 now check
len(device.Addresses) > 0before accessing the first element, providing a safe fallback ("no IP") when the array is empty. The huh-based selection flow is well-implemented and context-aware.
88-91: LGTM! Correct NodeID usage and error handling.The call to
internal.UpdateExitNodecorrectly passesnodeConnect, which now contains the NodeID (set on lines 48 or 83). Error handling provides helpful context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
internal/common.go (1)
170-176: Make post-update verification robust when ExitNodeStatus is nil.If the node isn’t in the current netmap, ExitNodeStatus can be nil even though the policy was applied; confirm via prefs before failing. This was raised earlier and remains unaddressed.
- status, err = localClient.Status(ctx) + status, err = localClient.Status(ctx) if err != nil { return fmt.Errorf("failed to get tailscale status: %w", err) } - if id != "" && (status.ExitNodeStatus == nil || status.ExitNodeStatus.ID != tailcfg.StableNodeID(id)) { - return errors.New("failed to set the exit node") - } + // Re-fetch prefs to validate policy application. + postPrefs, err := localClient.GetPrefs(ctx) + if err != nil { + return fmt.Errorf("failed to get prefs after update: %w", err) + } + if id != "" { + if status.ExitNodeStatus != nil { + if status.ExitNodeStatus.ID != tailcfg.StableNodeID(id) { + return fmt.Errorf("exit node mismatch: want %s, got %s", tailcfg.StableNodeID(id), status.ExitNodeStatus.ID) + } + } else if postPrefs.ExitNodeID != tailcfg.StableNodeID(id) { + return errors.New("exit node ID not applied") + } + } else { + if postPrefs.ExitNodeID != "" || status.ExitNodeStatus != nil { + return errors.New("failed to clear the exit node") + } + }
🧹 Nitpick comments (7)
internal/common.go (7)
36-38: Use sort.Strings for simpler intent.Directly sort the slice of strings.
- sort.Slice(regionNames, func(i, j int) bool { - return regionNames[i] < regionNames[j] - }) + sort.Strings(regionNames)
43-44: Fix exported doc comment style.Make the comment start with the function name to satisfy linters.
-// Function that uses huh to return an AWS region fetched from the aws sdk. +// SelectRegion uses huh to return an AWS region fetched via the AWS SDK.
74-76: LGTM: context-aware yes/no prompt.Minor: mirror doc comment style like above for linters.
-// Function that uses huh to return a boolean value. +// PromptYesNo uses huh to return a boolean value.Also applies to: 86-95
125-127: Prefer ipn.Running constant; include actual state in error.Avoid string literal comparison.
- if status.BackendState != "Running" { - return errors.New("tailscale is not running") - } + if status.BackendState != ipn.Running { + return fmt.Errorf("tailscale backend not running (state=%s)", status.BackendState) + }
150-166: Minimize MaskedPrefs to avoid stomping unrelated Prefs.Update only the field you intend to change.
- prefs, err := localClient.GetPrefs(ctx) - if err != nil { - return fmt.Errorf("failed to get prefs: %w", err) - } - - if id != "" { - fmt.Printf("Setting exit node to %s...\n", id) - prefs.ExitNodeID = tailcfg.StableNodeID(id) - } else { - fmt.Println("Clearing exit node...") - prefs.ClearExitNode() - } - _, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ - Prefs: *prefs, - ExitNodeIDSet: true, - }) + if id != "" { + fmt.Printf("Setting exit node to %s...\n", id) + _, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ + Prefs: ipn.Prefs{ExitNodeID: tailcfg.StableNodeID(id)}, + ExitNodeIDSet: true, + }) + } else { + fmt.Println("Clearing exit node...") + _, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{ + // zero value clears ExitNodeID when ExitNodeIDSet is true + Prefs: ipn.Prefs{}, + ExitNodeIDSet: true, + }) + } if err != nil { return fmt.Errorf("failed to set/unset exit node: %w", err) }
145-147: Avoid printing from internal packages.Return status strings or accept an io.Writer/logger so CLI decides how to display.
- fmt.Printf("Currently connected to exit node: %s\n", currentExitNodeName) + // return this info to caller for displayOption: change signature to accept an io.Writer or a logger interface.
Also applies to: 155-161
178-180: Reachability check is good; consider downgraded severity.If policy was applied but node is offline, consider returning a typed error so callers can choose between warn/fail.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
internal/common.go(3 hunks)
🔇 Additional comments (2)
internal/common.go (2)
53-61: LGTM: huh-based region select with context support.
129-148: Nil client guard is unnecessary—all call sites pass non-nil values.Both
UpdateExitNodecall sites construct thetsapi.Clientusing struct literals (&tsapi.Client{...}), which always produce non-nil pointers in Go. Indisconnect.go, the client is created at lines 17-20 and passed directly at line 23. Inconnect.go, the client is created at lines 23-27 and passed at line 88. No error handling paths result in nil clients being passed. The function parameterc *tsapi.Clientis a required contract fulfilled by all identified call sites.Likely an incorrect or invalid review comment.
Summary by CodeRabbit
New Features
UI/UX Improvements
Improvements
Chores