Skip to content

Add brev links command family for secure-link (HTTP port) exposure and management #437

Description

@theFong

Summary

Add a brev links command family to expose and manage secure links (public HTTP/HTTPS port exposure) for a workspace — the CLI is currently the only place this can't be done. Today secure links are UI-only: the environments UI renders them as an "HTTP services" / "Share a Service" table on the environment and registered-node pages, but brev has no way to list, create, or modify them.

Background

Dev-plane exposes the full secure-link surface on both EnvironmentService and ExternalNodeService as Connect-RPC, and the CLI's pinned buf.build proto already contains every one of these RPCs and the Port message — no server or proto work is needed:

RPC Effect
OpenHTTPPort(env_id, port_number, custom_hostname, http_protocol, authorized_emails, allow_public_unauthenticated) Expose a port as a public link → returns Port (idempotent; AlreadyExists on hostname conflict)
GetNetworkInfo(env_id) List ports incl. hostname, port_number, server_port, authorized_emails, allowed_sources, allow_public_unauthenticated, http_protocol
SetPortTarget(port_id, port_number) Retarget which server port a link forwards to
SetPortAllowedSources(port_id, cidr_blocks) IP/CIDR allowlist
SetHTTPPortProtocol(port_id, http_protocol) Toggle HTTP ⇄ HTTPS
SetHTTPPortAccess(port_id, authorized_emails, allow_public_unauthenticated) Email allowlist + public/no-auth toggle
ClosePort(port_id) Remove a link

The hostname returned by OpenHTTPPort is the full public URL (<subdomain>.<skybridge-endpoint>, e.g. myapp-r7zb490ph.apps.run.brev.nvidia.com) — the CLI should print it verbatim, never reconstruct it. Authz is enforced server-side per RPC (operation:environment:ports:maintain); the CLI only needs the user's existing token.

The CLI already has all the plumbing: register.NewEnvironmentServiceClient / register.NewNodeServiceClient (Bearer-token Connect clients), GetNetworkInfo is already exercised in pkg/cmd/refresh/sshaccess.go, and name→ID resolution exists via GetWorkspaceByNameOrID. This is a pure client-side feature.

Proposed interface

Noun-root parent command, mirroring the brev org {create,ls,set} precedent and the proposed brev launchable family (#391):

brev links ls <workspace>                    # list secure links (URLs, ports, access)
brev links open <workspace> [port]           # expose a port as a secure link
brev links close <workspace> <id|hostname>   # remove a link
brev links set-target <workspace> <id> <port>
brev links set-access <workspace> <id> --emails a@x,b@y --public|--private
brev links set-sources <workspace> <id> --cidr 0.0.0.0/0
brev links set-protocol <workspace> <id> --https|--http

brev links open flags:

--hostname NAME   # custom subdomain prefix (default: derived from port + env id, matching the UI)
--https           # expose as HTTPS (default)
--http            # expose as plain HTTP
--public          # allow unauthenticated public access
--emails a@x,b@y  # authorized emails (default: current user)

Feature set (acceptance criteria):

  • brev links ls <workspace> lists links with URL, public port, target port, protocol, and access (emails / public / CIDRs) — reusing the GetNetworkInfo pattern already in refresh/sshaccess.go
  • brev links open prints the returned public URL; idempotent re-open returns the existing link
  • brev links close / set-* map 1:1 to the RPCs above
  • --json output for ls and a bare-URL piped output for open (per the composable-CLI PRD)
  • External-node support via --node NAME (resolves via helpers.ResolveNodeByName, the grant-ssh/revoke-ssh pattern) — dev-plane's ExternalNodeService exposes the same RPCs

Debate — how this feels alongside the existing CLI

Noun parent vs. flat verbs. The CLI is mostly verb-first (start, stop, create, open, exec) but already uses noun parents where an operation has a real surface: org {set,ls,create}, ls orgs, and the pending launchable {create,deploy} (#391). Secure links have 6+ operations, so a flat set of top-level verbs (expose, unexpose, link-target, …) would add ~6 commands to an already ~40-command brev help and read as noise. brev links {ls,open,close,set-*} keeps the top level clean and groups by intent. This is the strongest argument for the parent.

links vs secure-links vs expose. "Secure links" is the product term (the authz operations are literally operation:environment:secureLinks:*), so secure-links is the most explicit option but verbose in daily use. links is shorter, reads naturally (brev links ls my-ws), and the Short help text can carry the full term. Flat expose/unexpose convey only the create side and leave listing (links?) and modification without a home — rejected. Recommendation: links root, with secure-links noted as the explicit alias if maintainers prefer product fidelity.

open collision. brev open already means "open an editor on the instance." Inside brev links, open = "open a new link" — which actually matches the RPC (OpenHTTPPort) and reads correctly in context, but the collision is worth a deliberate call. Alternative: brev links create (also fine, just diverges from the RPC name). Open question for maintainers.

Relationship to port-forward. port-forward is a local, private SSH tunnel; a secure link is a public, HTTPS endpoint. Both belong in the "Instance Access" section of brev help (alongside copy, exec, open, shell, grant-ssh). To keep the boundary obvious, help text should cross-reference: e.g. port-forward "private/local", links open "public/shared". Without this, users will reasonably try port-forward to publish a service and be confused.

Composability (the PRD direction). brev links ls --json should emit structured rows so brev links ls my-ws --json | jq '.[] | select(.protocol=="HTTPS")' works, and brev links open my-ws 8080 | xargs open should yield a bare URL when stdout is piped (pipe detection is already a stated design principle). Optionally support stdin passthrough so brev ls | brev links ls lists links across many workspaces — consistent with brev stop/brev delete consuming instance names from stdin.

Implementation notes (low risk, all client-side)

  • New package pkg/cmd/links/ following the portforward/grantssh layout: per-command Store interface, completions.GetAllWorkspaceNameCompletionHandler, cmderrors.TransformToValidationError, breverrors.WrapAndTrace, terminal colors via t.Vprintf.
  • Annotate root links with map[string]string{"access": ""} so it lands in the "Instance Access" help section; subcommands annotated so they don't appear at top level (the orgsubcommand pattern).
  • Reuse register.NewEnvironmentServiceClient(store, config.GlobalConfig.GetBrevPublicAPIURL()) and the GetNetworkInfo call site in refresh/sshaccess.go as the reference implementation.
  • Hostname composition: replicate buildSecureLinkCustomHostname from the environments UI (name-or-port + -<envID> suffix) so CLI-created links look identical to UI-created ones.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions