Skip to content

Add custom domain support to tunnel management SDKs#605

Merged
DavidObando merged 1 commit intomainfrom
dev/davidobando/custom-domains
Apr 3, 2026
Merged

Add custom domain support to tunnel management SDKs#605
DavidObando merged 1 commit intomainfrom
dev/davidobando/custom-domains

Conversation

@DavidObando
Copy link
Copy Markdown
Member

Design

When a tunnel service deployment uses a custom domain (e.g. "app.github.dev"), the control-plane URL is derived by prepending "cp." to that domain, producing "https://cp.app.github.dev/". Because custom-domain deployments handle cluster routing at the infrastructure level, the SDK must not rewrite the hostname with a cluster ID the way it does for the standard multi-cluster "global.rel.tunnels.api.visualstudio.com" deployment.

Implementation (all five SDKs)

  1. Added an isCustomDomain flag to each management client, set automatically in the constructor when the service URI hostname starts with "cp.".

  2. The BuildUri / buildUri / build_uri method now skips the cluster-ID-to-hostname rewriting logic when isCustomDomain is true, leaving the hostname unchanged.

  3. Added a static factory method on each client to construct from a custom domain string:

    • C#: TunnelManagementClient.ForCustomDomain(domain, ...)
    • TypeScript: TunnelManagementHttpClient.forCustomDomain(domain, ...)
    • Go: NewManagerForCustomDomain(domain, ...)
    • Java: TunnelManagementClient.forCustomDomain(domain, ...)
    • Rust: new_tunnel_management_for_custom_domain(ua, domain)

    Each factory validates the domain, builds the "https://cp.{domain}/" service URI, and delegates to the existing constructor/builder.

Files changed

cs/src/Management/TunnelManagementClient.cs

  • Added isCustomDomain field, set in constructor.
  • BuildUri() skips ReplaceTunnelServiceHostnameClusterId when custom domain is detected.
  • Added ForCustomDomain() static factory.

ts/src/management/tunnelManagementHttpClient.ts

  • Added isCustomDomain field, set in constructor.
  • buildUri() skips cluster-ID hostname manipulation.
  • Added forCustomDomain() static factory.

go/tunnels/manager.go

  • Added isCustomDomain field on Manager struct.
  • buildUri() skips cluster-ID prepend when true.
  • Added NewManagerForCustomDomain() function.

java/src/.../management/TunnelManagementClient.java

  • Added isCustomDomain field, set in constructor.
  • buildUri() skips cluster-ID manipulation.
  • Added forCustomDomain() static factory.

rs/src/management/http_client.rs

  • Added is_custom_domain to struct, builder, and From impl.
  • build_uri() skips cluster-ID rewriting.
  • Added new_tunnel_management_for_custom_domain() builder fn.
  • environment() setter auto-detects "cp." prefix.

Tests added

Each SDK has two new tests:

  • Custom domain URI keeps "cp.{domain}" hostname unchanged.
  • Standard service URI still rewrites hostname with cluster ID.

C#: CustomDomainDoesNotModifyHostname,
StandardServiceUriReplacesClusterIdInHostname
TS: customDomainDoesNotModifyHostname,
standardServiceUriReplacesClusterIdInHostname
Go: TestCustomDomainDoesNotModifyHostname,
TestStandardServiceUriReplacesClusterId
Java: forCustomDomainCreatesClient,
forCustomDomainRejectsBlank
Rust: custom_domain_does_not_modify_hostname,
standard_service_uri_replaces_cluster_id

Other Tasks:

  • If you updated the Go SDK did you update the PackageVersion in tunnels.go
  • If you updated the TS SDK did you update the dependencies in package.json for connections and management to require a dependency that is > the current published version(Found using npm view @microsoft/dev-tunnels-contracts). This will fix issues where yarn will pull the old version of packages and will cause mismatched dependencies. See example PR

Design
------
When a tunnel service deployment uses a custom domain (e.g.
"app.github.dev"), the control-plane URL is derived by prepending
"cp." to that domain, producing "https://cp.app.github.dev/".
Because custom-domain deployments handle cluster routing at the
infrastructure level, the SDK must not rewrite the hostname with a
cluster ID the way it does for the standard multi-cluster
"global.rel.tunnels.api.visualstudio.com" deployment.

Implementation (all five SDKs)
------------------------------
1. Added an `isCustomDomain` flag to each management client, set
   automatically in the constructor when the service URI hostname
   starts with "cp.".

2. The `BuildUri` / `buildUri` / `build_uri` method now skips
   the cluster-ID-to-hostname rewriting logic when `isCustomDomain`
   is true, leaving the hostname unchanged.

3. Added a static factory method on each client to construct from a
   custom domain string:
   - C#:         TunnelManagementClient.ForCustomDomain(domain, ...)
   - TypeScript:  TunnelManagementHttpClient.forCustomDomain(domain, ...)
   - Go:         NewManagerForCustomDomain(domain, ...)
   - Java:       TunnelManagementClient.forCustomDomain(domain, ...)
   - Rust:       new_tunnel_management_for_custom_domain(ua, domain)

   Each factory validates the domain, builds the "https://cp.{domain}/"
   service URI, and delegates to the existing constructor/builder.

Files changed
-------------
cs/src/Management/TunnelManagementClient.cs
  - Added `isCustomDomain` field, set in constructor.
  - BuildUri() skips ReplaceTunnelServiceHostnameClusterId when
    custom domain is detected.
  - Added ForCustomDomain() static factory.

ts/src/management/tunnelManagementHttpClient.ts
  - Added `isCustomDomain` field, set in constructor.
  - buildUri() skips cluster-ID hostname manipulation.
  - Added forCustomDomain() static factory.

go/tunnels/manager.go
  - Added `isCustomDomain` field on Manager struct.
  - buildUri() skips cluster-ID prepend when true.
  - Added NewManagerForCustomDomain() function.

java/src/.../management/TunnelManagementClient.java
  - Added `isCustomDomain` field, set in constructor.
  - buildUri() skips cluster-ID manipulation.
  - Added forCustomDomain() static factory.

rs/src/management/http_client.rs
  - Added `is_custom_domain` to struct, builder, and From impl.
  - build_uri() skips cluster-ID rewriting.
  - Added new_tunnel_management_for_custom_domain() builder fn.
  - environment() setter auto-detects "cp." prefix.

Tests added
-----------
Each SDK has two new tests:
  - Custom domain URI keeps "cp.{domain}" hostname unchanged.
  - Standard service URI still rewrites hostname with cluster ID.

C#:   CustomDomainDoesNotModifyHostname,
      StandardServiceUriReplacesClusterIdInHostname
TS:   customDomainDoesNotModifyHostname,
      standardServiceUriReplacesClusterIdInHostname
Go:   TestCustomDomainDoesNotModifyHostname,
      TestStandardServiceUriReplacesClusterId
Java: forCustomDomainCreatesClient,
      forCustomDomainRejectsBlank
Rust: custom_domain_does_not_modify_hostname,
      standard_service_uri_replaces_cluster_id
Comment thread cs/src/Management/TunnelManagementClient.cs
Comment thread cs/src/Management/TunnelManagementClient.cs
@DavidObando DavidObando merged commit 5d1efcc into main Apr 3, 2026
9 checks passed
@DavidObando DavidObando deleted the dev/davidobando/custom-domains branch April 3, 2026 21:06
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