Skip to content

[client] Add Expose support to embed library#5695

Merged
pappz merged 8 commits intomainfrom
feature/embed-expose
Mar 30, 2026
Merged

[client] Add Expose support to embed library#5695
pappz merged 8 commits intomainfrom
feature/embed-expose

Conversation

@pappz
Copy link
Copy Markdown
Collaborator

@pappz pappz commented Mar 25, 2026

Describe your changes

Add Expose API to the embed library, allowing programmatic creation and
management of service exposures (HTTP, HTTPS, TCP, UDP, TLS) from embedded
client code.

Introduces ExposeSession with a blocking Wait method that keeps the exposure
alive until the context is cancelled. Extracts ProtocolType into a shared
expose package for reuse across CLI and embed layers.

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features

    • Expose local services publicly with support for HTTP, HTTPS, TCP, UDP, and TLS.
    • Client API to create and manage exposures, returning sessions with domain, service name, and service URL.
    • Session "wait" to keep exposures alive until explicitly stopped.
  • Bug Fixes / Improvements

    • Improved protocol parsing using a typed protocol enum and clearer validation errors for unsupported protocol values.

Add ability to expose local services via the NetBird reverse proxy
from embedded client code.

Introduce ExposeSession with a blocking Wait method that keeps
the session alive until the context is cancelled.

Extract ProtocolType with ParseProtocolType into the expose package
and use it across CLI and embed layers.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a typed ProtocolType enum and parser; updates internal expose request/manager to use the enum; introduces an embed Expose API and ExposeSession with Wait; updates CLI to parse protocols via the new enum and switch on enum values; updates tests and minor reorderings.

Changes

Cohort / File(s) Summary
Protocol Type & Parser
client/internal/expose/protocol.go
Adds ProtocolType enum constants and ParseProtocolType(string) (ProtocolType, error) to normalize and validate protocol strings.
Internal Expose Core
client/internal/expose/manager.go, client/internal/expose/request.go, client/internal/expose/manager_test.go
Changes Request.Protocol from int to ProtocolType; updates conversions to/from int in request construction and mapping; adjusts tests to expect ProtocolType; groups a const.
Public Embed API
client/embed/expose.go, client/embed/embed.go
Adds exported protocol constants, type aliases, ExposeRequest/ExposeSession, (*ExposeSession).Wait(ctx) and (*Client).Expose(ctx, req); reorders a type alias declaration.
CLI Command
client/cmd/expose.go
Refactors toExposeProtocol() to use expose.ParseProtocolType() and switch on parsed enum values; changes parse and unhandled-protocol error paths and messages.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant CLI as CLI (toExposeProtocol)
    participant Client as Client.Expose
    participant Engine as Engine
    participant Manager as ExposeManager
    participant Session as ExposeSession

    User->>CLI: provide protocol string
    CLI->>CLI: ParseProtocolType(str) -> ProtocolType
    User->>Client: Expose(ctx, ExposeRequest)
    Client->>Engine: getEngine()
    Engine-->>Client: engine
    Client->>Manager: engine.GetExposeManager()
    Manager-->>Client: manager
    Client->>Manager: mgr.Expose(ctx, req)
    Manager-->>Client: expose response
    Client->>Session: create ExposeSession{Domain,ServiceName,ServiceURL,mgr}
    Client-->>User: return *ExposeSession
    User->>Session: Wait(ctx)
    Session->>Manager: mgr.KeepAlive(ctx, Domain)
    Manager-->>Session: blocks until ctx cancelled or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • pappz
  • pascal-fischer

Poem

🐰 I hopped through strings to find each proper name,

Gave protocols names so parsing wasn't a game,
I cradle sessions, keepalive the tune,
Domains snug as carrots beneath the moon,
The rabbit signs off — expose hums in the room.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding Expose support to the embed library, which is the primary objective reflected in all code changes.
Description check ✅ Passed The PR description provides a clear summary of changes and marks documentation status, but the Issue ticket and Stack sections are entirely empty.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/embed-expose

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pappz pappz marked this pull request as ready for review March 25, 2026 14:00
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
client/internal/expose/protocol.go (1)

8-22: Consider adding a String() method for debugging and logging.

Adding a String() method to ProtocolType would improve debugging and logging output. Currently, logging a ProtocolType will show the numeric value rather than a human-readable name.

💡 Suggested implementation
// String returns the string representation of the protocol type.
func (p ProtocolType) String() string {
	switch p {
	case ProtocolHTTP:
		return "http"
	case ProtocolHTTPS:
		return "https"
	case ProtocolTCP:
		return "tcp"
	case ProtocolUDP:
		return "udp"
	case ProtocolTLS:
		return "tls"
	default:
		return fmt.Sprintf("unknown(%d)", p)
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/internal/expose/protocol.go` around lines 8 - 22, Add a String()
method on ProtocolType to return human-readable names for logging/debugging:
implement func (p ProtocolType) String() string { ... } that switches on
ProtocolHTTP, ProtocolHTTPS, ProtocolTCP, ProtocolUDP, ProtocolTLS and returns
"http", "https", "tcp", "udp", "tls" respectively, and returns a formatted
fallback like "unknown(%d)" for defaults; ensure you import fmt if needed and
place the method near the ProtocolType definition so logs calling
ProtocolType.String() or fmt.Sprintf("%v", p) produce readable output.
client/cmd/expose.go (1)

136-143: Consider consolidating protocol validation logic.

isProtocolValid duplicates the validation already performed by expose.ParseProtocolType. Similarly, isClusterProtocol and isPortBasedProtocol use string-based checks while toExposeProtocol now uses the typed enum. Consider adding helper methods to ProtocolType (e.g., IsCluster(), IsPortBased()) to centralize this logic and prevent drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/cmd/expose.go` around lines 136 - 143, isProtocolValid duplicates
expose.ParseProtocolType and string checks in isClusterProtocol/isPortBased
drift from the typed enum; add helper methods on ProtocolType (e.g., func (p
ProtocolType) IsCluster() bool and func (p ProtocolType) IsPortBased() bool and
optionally IsValid()) in the expose package, implement their logic against the
enum values, then replace string-based checks in isProtocolValid,
isClusterProtocol, isPortBased and update toExposeProtocol to use these
ProtocolType helpers (or call ParseProtocolType once and use the resulting
ProtocolType.IsCluster/IsPortBased) so all protocol logic is centralized on the
ProtocolType methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@client/cmd/expose.go`:
- Around line 136-143: isProtocolValid duplicates expose.ParseProtocolType and
string checks in isClusterProtocol/isPortBased drift from the typed enum; add
helper methods on ProtocolType (e.g., func (p ProtocolType) IsCluster() bool and
func (p ProtocolType) IsPortBased() bool and optionally IsValid()) in the expose
package, implement their logic against the enum values, then replace
string-based checks in isProtocolValid, isClusterProtocol, isPortBased and
update toExposeProtocol to use these ProtocolType helpers (or call
ParseProtocolType once and use the resulting ProtocolType.IsCluster/IsPortBased)
so all protocol logic is centralized on the ProtocolType methods.

In `@client/internal/expose/protocol.go`:
- Around line 8-22: Add a String() method on ProtocolType to return
human-readable names for logging/debugging: implement func (p ProtocolType)
String() string { ... } that switches on ProtocolHTTP, ProtocolHTTPS,
ProtocolTCP, ProtocolUDP, ProtocolTLS and returns "http", "https", "tcp", "udp",
"tls" respectively, and returns a formatted fallback like "unknown(%d)" for
defaults; ensure you import fmt if needed and place the method near the
ProtocolType definition so logs calling ProtocolType.String() or
fmt.Sprintf("%v", p) produce readable output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7a1a300-fcf9-40d2-b9f3-d0b37db0f3a8

📥 Commits

Reviewing files that changed from the base of the PR and between 9aaa05e and 1c80258.

📒 Files selected for processing (6)
  • client/cmd/expose.go
  • client/embed/embed.go
  • client/embed/expose.go
  • client/internal/expose/manager.go
  • client/internal/expose/protocol.go
  • client/internal/expose/request.go

Comment thread client/embed/expose.go Outdated
Comment thread client/internal/expose/manager.go
Comment thread client/cmd/expose.go
@pappz pappz requested a review from lixmal March 26, 2026 20:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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 (1)
client/embed/expose.go (1)

23-27: Consider decoupling embed API from internal types.

Using type aliases (ExposeRequest / ExposeProtocolType) directly to client/internal/expose makes internal refactors potentially API-breaking for embed consumers. Defining explicit embed-facing types plus conversion at the boundary would improve long-term API stability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/embed/expose.go` around lines 23 - 27, The public embed API currently
aliases internal types (ExposeRequest and ExposeProtocolType) to
client/internal/expose, which couples the public surface to internal refactors;
change these aliases to explicit embed-facing types (e.g., define a new
struct/type ExposeRequest and new type ExposeProtocolType in this package) and
implement conversion functions (e.g., ToInternalExposeRequest and
FromInternalExposeRequest) that translate between the embed types and
client/internal/expose types at the boundary (use these converters wherever the
package currently passes alias types to internal code such as in any functions
referencing ExposeRequest or ExposeProtocolType).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/embed/expose.go`:
- Around line 39-40: The Wait method on ExposeSession must guard against a nil
receiver or missing manager to avoid panics: in the ExposeSession.Wait function
check if s == nil or s.mgr == nil and return a clear error (e.g., "nil
ExposeSession" or "missing manager") instead of calling s.mgr.KeepAlive; keep
the call to s.mgr.KeepAlive(ctx, s.Domain) only after those checks. Ensure
references to the ExposeSession type and the Wait method and the s.mgr.KeepAlive
call are used to locate and update the code.

---

Nitpick comments:
In `@client/embed/expose.go`:
- Around line 23-27: The public embed API currently aliases internal types
(ExposeRequest and ExposeProtocolType) to client/internal/expose, which couples
the public surface to internal refactors; change these aliases to explicit
embed-facing types (e.g., define a new struct/type ExposeRequest and new type
ExposeProtocolType in this package) and implement conversion functions (e.g.,
ToInternalExposeRequest and FromInternalExposeRequest) that translate between
the embed types and client/internal/expose types at the boundary (use these
converters wherever the package currently passes alias types to internal code
such as in any functions referencing ExposeRequest or ExposeProtocolType).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f7c87a9a-473a-44a0-bb66-2fa44537e9d0

📥 Commits

Reviewing files that changed from the base of the PR and between 5582489 and 40cc469.

📒 Files selected for processing (4)
  • client/cmd/expose.go
  • client/embed/embed.go
  • client/embed/expose.go
  • client/internal/expose/manager.go
✅ Files skipped from review due to trivial changes (1)
  • client/internal/expose/manager.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/embed/embed.go
  • client/cmd/expose.go

Comment thread client/embed/expose.go
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
client/embed/expose.go (1)

3-7: ⚠️ Potential issue | 🔴 Critical

Import errors and remove the trailing brace.

Line 41 uses errors.New() without importing the errors package, and line 45 contains an extra closing brace. The file will not compile as-is.

Proposed fix
 import (
 	"context"
+	"errors"
 
 	"github.com/netbirdio/netbird/client/internal/expose"
 )
@@
 func (s *ExposeSession) Wait(ctx context.Context) error {
 	if s == nil || s.mgr == nil {
 		return errors.New("expose session is not initialized")
 	}
 	return s.mgr.KeepAlive(ctx, s.Domain)
 }
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/embed/expose.go` around lines 3 - 7, The import block in
client/embed/expose.go is missing the "errors" package needed by the call to
errors.New(), and there is an extra closing brace at the end of the file that
breaks compilation; update the import list to include "errors" alongside
"context" and "github.com/netbirdio/netbird/client/internal/expose", and remove
the stray trailing brace after the function/variable that calls errors.New() so
the file parses and compiles correctly.
🧹 Nitpick comments (1)
client/embed/expose.go (1)

10-19: Clarify that these constants describe the exposed/frontend mode.

The current comments can be read as “the local service must speak HTTPS/TLS.” For a new embed-facing API, it would be clearer to say these values select how the public endpoint is exposed, because backend transport is handled separately internally.

Based on learnings, Service.Mode controls the frontend listener type, while Target.Protocol is a separate backend concern.

Also applies to: 25-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/embed/expose.go` around lines 10 - 19, Update the comments for the
ExposeProtocol* constants (ExposeProtocolHTTP, ExposeProtocolHTTPS,
ExposeProtocolTCP, ExposeProtocolUDP, ExposeProtocolTLS) to make clear these
values select how the public/frontend endpoint is exposed (the frontend/listener
mode), not the backend transport; mention that backend transport is controlled
separately by Target.Protocol and that Service.Mode controls the frontend
listener type—apply the same clarification to the other related comments at the
25-26 area.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@client/embed/expose.go`:
- Around line 3-7: The import block in client/embed/expose.go is missing the
"errors" package needed by the call to errors.New(), and there is an extra
closing brace at the end of the file that breaks compilation; update the import
list to include "errors" alongside "context" and
"github.com/netbirdio/netbird/client/internal/expose", and remove the stray
trailing brace after the function/variable that calls errors.New() so the file
parses and compiles correctly.

---

Nitpick comments:
In `@client/embed/expose.go`:
- Around line 10-19: Update the comments for the ExposeProtocol* constants
(ExposeProtocolHTTP, ExposeProtocolHTTPS, ExposeProtocolTCP, ExposeProtocolUDP,
ExposeProtocolTLS) to make clear these values select how the public/frontend
endpoint is exposed (the frontend/listener mode), not the backend transport;
mention that backend transport is controlled separately by Target.Protocol and
that Service.Mode controls the frontend listener type—apply the same
clarification to the other related comments at the 25-26 area.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48dea32d-b00b-485c-ad5a-24d7543dfe02

📥 Commits

Reviewing files that changed from the base of the PR and between 40cc469 and 3989f80.

📒 Files selected for processing (1)
  • client/embed/expose.go

Comment thread client/embed/expose.go
lixmal
lixmal previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/embed/expose.go`:
- Around line 3-7: The build fails because errors.New is used but the "errors"
package is not imported; update the import block in this file to include the
standard "errors" package so the call to errors.New() compiles (i.e., add
"errors" to the import list alongside context and the internal/expose import).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f72eb1a2-fe48-4003-8858-34b699926b0d

📥 Commits

Reviewing files that changed from the base of the PR and between 3989f80 and b1a8fa6.

📒 Files selected for processing (1)
  • client/embed/expose.go

Comment thread client/embed/expose.go
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@sonarqubecloud
Copy link
Copy Markdown

@pappz pappz requested a review from lixmal March 29, 2026 15:11
@pappz pappz merged commit c522506 into main Mar 30, 2026
79 of 83 checks passed
@pappz pappz deleted the feature/embed-expose branch March 30, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants