Repository navigation
fix(BRE2-1216): restore brev connect node status #506
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,11 @@ import ( | |
| "fmt" | ||
| "os" | ||
| "sync" | ||
| "time" | ||
|
|
||
| nodev1 "buf.build/gen/go/brevdev/devplane/protocolbuffers/go/devplaneapi/v1" | ||
| "connectrpc.com/connect" | ||
| "google.golang.org/protobuf/proto" | ||
|
|
||
| "github.com/brevdev/brev-cli/pkg/analytics" | ||
| "github.com/brevdev/brev-cli/pkg/auth" | ||
|
|
@@ -429,7 +431,7 @@ func (ls Ls) RunWorkspaces(cliAuth auth.CLIAuth, org *entity.Organization, showA | |
| go func() { | ||
| defer wg.Done() | ||
| var err error | ||
| nodes, err = ls.listNodes(org) | ||
| nodes, err = ls.listNodesWithConnectivity(org) | ||
| if err != nil { | ||
| if featureflag.Debug() { | ||
| _, _ = fmt.Fprintf(os.Stderr, "debug: failed to list Brev Connect machines: %v\n", err) | ||
|
|
@@ -726,6 +728,57 @@ type NodeInfo struct { | |
| Status string `json:"status"` | ||
| } | ||
|
|
||
| const nodeStatusFetchConcurrency = 5 | ||
|
|
||
| // nodeStatusFetchTimeout bounds the whole call so the user isn't waiting forever. Nodes that miss the deadline | ||
| // keep their list-provided status. | ||
| const nodeStatusFetchTimeout = 10 * time.Second | ||
|
|
||
| // enrichNodesWithConnectivity re-fetches each node via GetNode, which returns | ||
| // live connectivity info that ListNodes cannot provide. Fetches run in parallel | ||
| // with at most nodeStatusFetchConcurrency in flight. Nodes whose GetNode fails | ||
| // or times out keep their list-provided connectivity info. | ||
| func (ls Ls) enrichNodesWithConnectivity(ctx context.Context, nodes []*nodev1.ExternalNode) []*nodev1.ExternalNode { | ||
| if len(nodes) == 0 { | ||
| return nodes | ||
| } | ||
| client := register.NewNodeServiceClient(ls.lsStore, config.GlobalConfig.GetBrevPublicAPIURL()) | ||
| ctx, cancel := context.WithTimeout(ctx, nodeStatusFetchTimeout) | ||
| defer cancel() | ||
|
|
||
| sem := make(chan struct{}, nodeStatusFetchConcurrency) | ||
| var wg sync.WaitGroup | ||
| for _, node := range nodes { | ||
| wg.Add(1) | ||
| go func(node *nodev1.ExternalNode) { | ||
| defer wg.Done() | ||
| select { | ||
| case sem <- struct{}{}: | ||
| case <-ctx.Done(): | ||
| if featureflag.Debug() { | ||
| _, _ = fmt.Fprintf(os.Stderr, "debug: connectivity fetch canceled for node %s: %v\n", node.GetExternalNodeId(), ctx.Err()) | ||
| } | ||
| return | ||
| } | ||
| defer func() { <-sem }() | ||
| resp, err := client.GetNode(ctx, connect.NewRequest(&nodev1.GetNodeRequest{ | ||
| ExternalNodeId: node.GetExternalNodeId(), | ||
| })) | ||
| if err != nil { | ||
| if featureflag.Debug() { | ||
| _, _ = fmt.Fprintf(os.Stderr, "debug: failed to fetch connectivity for node %s: %v\n", node.GetExternalNodeId(), err) | ||
| } | ||
| return | ||
| } | ||
| if enriched := resp.Msg.GetExternalNode(); enriched != nil { | ||
| proto.Merge(node, enriched) | ||
| } | ||
| }(node) | ||
| } | ||
| wg.Wait() | ||
| return nodes | ||
| } | ||
|
|
||
| func (ls Ls) listNodes(org *entity.Organization) ([]*nodev1.ExternalNode, error) { | ||
| client := register.NewNodeServiceClient(ls.lsStore, config.GlobalConfig.GetBrevPublicAPIURL()) | ||
| resp, err := client.ListNodes(context.Background(), connect.NewRequest(&nodev1.ListNodesRequest{ | ||
|
|
@@ -737,9 +790,18 @@ func (ls Ls) listNodes(org *entity.Organization) ([]*nodev1.ExternalNode, error) | |
| return resp.Msg.GetItems(), nil | ||
| } | ||
|
|
||
| // TODO should this be optional? | ||
| func (ls Ls) listNodesWithConnectivity(org *entity.Organization) ([]*nodev1.ExternalNode, error) { | ||
| nodes, err := ls.listNodes(org) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return ls.enrichNodesWithConnectivity(context.Background(), nodes), nil | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Inspect the authenticated client's timeout and the node client factory.
ast-grep outline pkg/cmd/register/rpcclient.go --match 'newAuthenticatedHTTPClient|NewNodeServiceClient' --view expanded
rg -n -C 5 'func newAuthenticatedHTTPClient|Timeout:|WithTimeout|WithDeadline|NewNodeServiceClient' pkg/cmd/registerRepository: brevdev/brev-cli Length of output: 9468 🏁 Script executed: set -e
printf '%s\n' '--- changed hunks ---'
git diff --no-ext-diff --unified=20 a0190d24655f57f327778f7708d8a6f634afdb1a 2b290eea3f723df1789fef7f7780ac0bd8b4166d -- pkg/cmd/ls/ls.go
printf '%s\n' '--- ls outlines ---'
ast-grep outline pkg/cmd/ls/ls.go --view expanded
printf '%s\n' '--- enrichment and callers ---'
rg -n -C 12 -F -- \
-e 'enrichNodesWithConnectivity' \
-e 'listNodesWithConnectivity' \
-e 'GetNode(' \
-e 'func (ls ' \
pkg/cmd/ls/ls.goRepository: brevdev/brev-cli Length of output: 11656 🏁 Script executed: sed -n '680,810p' pkg/cmd/ls/ls.go
rg -n -C 20 'RunNodes|RunWorkspaces|enrichNodesWithConnectivity|listNodesWithConnectivity|GetNode' pkg/cmd/ls/ls.goRepository: brevdev/brev-cli Length of output: 14145 🏁 Script executed: set -e
nl -ba pkg/cmd/ls/ls.go | sed -n '700,810p'
printf '%s\n' '--- callers ---'
rg -n -C 25 -F -- 'listNodesWithConnectivity' 'enrichNodesWithConnectivity' 'RunNodes' 'RunWorkspaces' pkg/cmd/ls/ls.goRepository: brevdev/brev-cli Length of output: 9665 Bound the node connectivity enrichment phase. The authenticated HTTP client has no request timeout, and 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // RunNodes lists Brev Connect machines for the given org. | ||
| func (ls Ls) RunNodes(org *entity.Organization) error { | ||
| nodes, err := ls.listNodes(org) | ||
| nodes, err := ls.listNodesWithConnectivity(org) | ||
| if err != nil { | ||
| return breverrors.WrapAndTrace(err) | ||
| } | ||
|
|
||
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: brevdev/brev-cli
Length of output: 17007
🏁 Script executed:
Repository: brevdev/brev-cli
Length of output: 30829
🏁 Script executed:
Repository: brevdev/brev-cli
Length of output: 33362
🏁 Script executed:
Repository: brevdev/brev-cli
Length of output: 28652
🏁 Script executed:
Repository: brevdev/brev-cli
Length of output: 5711
Replace the connectivity snapshot instead of merging it.
ConnectivityInfo.Statusis an implicit-presenceproto3enum. A successfulGetNoderesponse can contain its zero value.proto.Mergeskips that value and preserves the status fromListNodes.Use the generated setter after merging the other node fields.
🐛 Suggested fix
if enriched := resp.Msg.GetExternalNode(); enriched != nil { proto.Merge(node, enriched) + node.SetConnectivityInfo(enriched.GetConnectivityInfo()) }📝 Committable suggestion
🤖 Prompt for AI Agents