From f306379771c81349912e26142eaa21c8cb44e7be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 Aug 2025 07:35:03 +0000 Subject: [PATCH 1/4] Initial plan From 79af7b913a07e0142a26892823308eb4a0acbb92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 Aug 2025 07:59:17 +0000 Subject: [PATCH 2/4] Implement hostname length validation for Coder workspace apps Co-authored-by: matifali <10648092+matifali@users.noreply.github.com> --- coderd/database/db2sdk/db2sdk.go | 30 ++++- .../db2sdk/hostname_validation_test.go | 122 ++++++++++++++++++ 2 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 coderd/database/db2sdk/hostname_validation_test.go diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 48f6ff44af70f..21d9ece102a0f 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -539,6 +539,23 @@ func AppSubdomain(dbApp database.WorkspaceApp, agentName, workspaceName, ownerNa }.String() } +// validateAppHostnameLength checks if any segment of the app hostname exceeds the DNS label limit of 63 characters. +// Returns true if the hostname is valid, false if any segment is too long. +func validateAppHostnameLength(subdomainName string) bool { + if subdomainName == "" { + return true + } + + // Split the hostname into segments by '--' (the format is app--agent--workspace--user) + segments := strings.Split(subdomainName, "--") + for _, segment := range segments { + if len(segment) > 63 { + return false + } + } + return true +} + func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus, agent database.WorkspaceAgent, ownerName string, workspace database.Workspace) []codersdk.WorkspaceApp { sort.Slice(dbApps, func(i, j int) bool { if dbApps[i].DisplayOrder != dbApps[j].DisplayOrder { @@ -558,6 +575,15 @@ func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { statuses := statusesByAppID[dbApp.ID] + subdomainName := AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName) + appHealth := codersdk.WorkspaceAppHealth(dbApp.Health) + + // Check if this is a subdomain app with hostname length issues + if dbApp.Subdomain && subdomainName != "" && !validateAppHostnameLength(subdomainName) { + // Override health to unhealthy if hostname exceeds DNS limits + appHealth = codersdk.WorkspaceAppHealthUnhealthy + } + apps = append(apps, codersdk.WorkspaceApp{ ID: dbApp.ID, URL: dbApp.Url.String, @@ -567,14 +593,14 @@ func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus Command: dbApp.Command.String, Icon: dbApp.Icon, Subdomain: dbApp.Subdomain, - SubdomainName: AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName), + SubdomainName: subdomainName, SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel), Healthcheck: codersdk.Healthcheck{ URL: dbApp.HealthcheckUrl, Interval: dbApp.HealthcheckInterval, Threshold: dbApp.HealthcheckThreshold, }, - Health: codersdk.WorkspaceAppHealth(dbApp.Health), + Health: appHealth, Group: dbApp.DisplayGroup.String, Hidden: dbApp.Hidden, OpenIn: codersdk.WorkspaceAppOpenIn(dbApp.OpenIn), diff --git a/coderd/database/db2sdk/hostname_validation_test.go b/coderd/database/db2sdk/hostname_validation_test.go new file mode 100644 index 0000000000000..a4d1891025aea --- /dev/null +++ b/coderd/database/db2sdk/hostname_validation_test.go @@ -0,0 +1,122 @@ +package db2sdk + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/codersdk" +) + +func TestValidateAppHostnameLength(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + subdomainName string + expected bool + }{ + { + name: "empty hostname", + subdomainName: "", + expected: true, + }, + { + name: "valid short hostname", + subdomainName: "app--agent--workspace--user", + expected: true, + }, + { + name: "valid hostname with max length segment", + subdomainName: "a12345678901234567890123456789012345678901234567890123456789012--agent--workspace--user", // 63 chars in first segment + expected: true, + }, + { + name: "invalid hostname with long app name", + subdomainName: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail--agent--workspace--user", // 78 chars in first segment + expected: false, + }, + { + name: "invalid hostname with long agent name", + subdomainName: "app--toolongagentnamethatexceedsthednslimitof63charactersforsureandshouldfail--workspace--user", // 72 chars in agent segment + expected: false, + }, + { + name: "invalid hostname with long workspace name", + subdomainName: "app--agent--toolongworkspacenamethatexceedsthednslimitof63charactersforsureandshouldfail--user", // 77 chars in workspace segment + expected: false, + }, + { + name: "invalid hostname with long username", + subdomainName: "app--agent--workspace--toolongusernamethatexceedsthednslimitof63charactersforsureandshouldfail", // 72 chars in username segment + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := validateAppHostnameLength(tt.subdomainName) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestAppsWithHostnameLengthValidation(t *testing.T) { + t.Parallel() + + agent := database.WorkspaceAgent{ + ID: uuid.New(), + Name: "agent", + } + workspace := database.Workspace{ + ID: uuid.New(), + Name: "workspace", + } + ownerName := "user" + + tests := []struct { + name string + appSlug string + subdomain bool + expectedHealth codersdk.WorkspaceAppHealth + }{ + { + name: "non-subdomain app should not be affected", + appSlug: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail", + subdomain: false, + expectedHealth: codersdk.WorkspaceAppHealthHealthy, + }, + { + name: "short subdomain app should remain healthy", + appSlug: "app", + subdomain: true, + expectedHealth: codersdk.WorkspaceAppHealthHealthy, + }, + { + name: "long subdomain app should become unhealthy", + appSlug: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail", + subdomain: true, + expectedHealth: codersdk.WorkspaceAppHealthUnhealthy, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dbApps := []database.WorkspaceApp{ + { + ID: uuid.New(), + Slug: tt.appSlug, + DisplayName: "Test App", + Subdomain: tt.subdomain, + Health: database.WorkspaceAppHealthHealthy, // Start as healthy + }, + } + + apps := Apps(dbApps, []database.WorkspaceAppStatus{}, agent, ownerName, workspace) + require.Len(t, apps, 1) + require.Equal(t, tt.expectedHealth, apps[0].Health) + }) + } +} \ No newline at end of file From e1701cf4b9e46abb53421fee12ff5e5c3205355e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:03:52 +0000 Subject: [PATCH 3/4] Apply code formatting fixes --- coderd/database/db2sdk/db2sdk.go | 6 ++-- .../db2sdk/hostname_validation_test.go | 34 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index 21d9ece102a0f..62401f088c101 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -545,7 +545,7 @@ func validateAppHostnameLength(subdomainName string) bool { if subdomainName == "" { return true } - + // Split the hostname into segments by '--' (the format is app--agent--workspace--user) segments := strings.Split(subdomainName, "--") for _, segment := range segments { @@ -577,13 +577,13 @@ func Apps(dbApps []database.WorkspaceApp, statuses []database.WorkspaceAppStatus statuses := statusesByAppID[dbApp.ID] subdomainName := AppSubdomain(dbApp, agent.Name, workspace.Name, ownerName) appHealth := codersdk.WorkspaceAppHealth(dbApp.Health) - + // Check if this is a subdomain app with hostname length issues if dbApp.Subdomain && subdomainName != "" && !validateAppHostnameLength(subdomainName) { // Override health to unhealthy if hostname exceeds DNS limits appHealth = codersdk.WorkspaceAppHealthUnhealthy } - + apps = append(apps, codersdk.WorkspaceApp{ ID: dbApp.ID, URL: dbApp.Url.String, diff --git a/coderd/database/db2sdk/hostname_validation_test.go b/coderd/database/db2sdk/hostname_validation_test.go index a4d1891025aea..8c2552abd3071 100644 --- a/coderd/database/db2sdk/hostname_validation_test.go +++ b/coderd/database/db2sdk/hostname_validation_test.go @@ -14,44 +14,44 @@ func TestValidateAppHostnameLength(t *testing.T) { t.Parallel() tests := []struct { - name string + name string subdomainName string - expected bool + expected bool }{ { - name: "empty hostname", + name: "empty hostname", subdomainName: "", - expected: true, + expected: true, }, { - name: "valid short hostname", + name: "valid short hostname", subdomainName: "app--agent--workspace--user", - expected: true, + expected: true, }, { - name: "valid hostname with max length segment", + name: "valid hostname with max length segment", subdomainName: "a12345678901234567890123456789012345678901234567890123456789012--agent--workspace--user", // 63 chars in first segment - expected: true, + expected: true, }, { - name: "invalid hostname with long app name", + name: "invalid hostname with long app name", subdomainName: "toolongappnamethatexceedsthednslimitof63charactersforsureandshouldfail--agent--workspace--user", // 78 chars in first segment - expected: false, + expected: false, }, { - name: "invalid hostname with long agent name", + name: "invalid hostname with long agent name", subdomainName: "app--toolongagentnamethatexceedsthednslimitof63charactersforsureandshouldfail--workspace--user", // 72 chars in agent segment - expected: false, + expected: false, }, { - name: "invalid hostname with long workspace name", + name: "invalid hostname with long workspace name", subdomainName: "app--agent--toolongworkspacenamethatexceedsthednslimitof63charactersforsureandshouldfail--user", // 77 chars in workspace segment - expected: false, + expected: false, }, { - name: "invalid hostname with long username", + name: "invalid hostname with long username", subdomainName: "app--agent--workspace--toolongusernamethatexceedsthednslimitof63charactersforsureandshouldfail", // 72 chars in username segment - expected: false, + expected: false, }, } @@ -119,4 +119,4 @@ func TestAppsWithHostnameLengthValidation(t *testing.T) { require.Equal(t, tt.expectedHealth, apps[0].Health) }) } -} \ No newline at end of file +} From 179e1f9194f56d36268b080d6be177a0ac05d4e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:58:17 +0000 Subject: [PATCH 4/4] Add informative tooltip for hostname length validation in workspace apps Co-authored-by: matifali <10648092+matifali@users.noreply.github.com> --- .../resources/AppLink/AppLink.stories.tsx | 13 +++++++++++++ .../src/modules/resources/AppLink/AppLink.tsx | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/site/src/modules/resources/AppLink/AppLink.stories.tsx b/site/src/modules/resources/AppLink/AppLink.stories.tsx index 891ddd3c2af7d..9b94cac8dd355 100644 --- a/site/src/modules/resources/AppLink/AppLink.stories.tsx +++ b/site/src/modules/resources/AppLink/AppLink.stories.tsx @@ -155,6 +155,19 @@ export const HealthUnhealthy: Story = { }, }; +export const HealthUnhealthyDueToHostnameLength: Story = { + args: { + workspace: MockWorkspace, + app: { + ...MockWorkspaceApp, + health: "unhealthy", + subdomain: true, + subdomain_name: "very-long-application-name-that-exceeds-dns-limits-and-causes-failures--agent--workspace--user", + }, + agent: MockWorkspaceAgent, + }, +}; + export const InternalApp: Story = { args: { workspace: MockWorkspace, diff --git a/site/src/modules/resources/AppLink/AppLink.tsx b/site/src/modules/resources/AppLink/AppLink.tsx index 637f0287a4088..a82568c8860fd 100644 --- a/site/src/modules/resources/AppLink/AppLink.tsx +++ b/site/src/modules/resources/AppLink/AppLink.tsx @@ -17,6 +17,17 @@ import { AgentButton } from "../AgentButton"; import { BaseIcon } from "./BaseIcon"; import { ShareIcon } from "./ShareIcon"; +// Check if an app's hostname has segments that exceed DNS label limits (63 characters) +const hasHostnameLengthIssue = (app: TypesGen.WorkspaceApp): boolean => { + if (!app.subdomain || !app.subdomain_name) { + return false; + } + + // Split by '--' to get hostname segments (format: app--agent--workspace--user) + const segments = app.subdomain_name.split("--"); + return segments.some(segment => segment.length > 63); +}; + export const DisplayAppNameMap: Record = { port_forwarding_helper: "Ports", ssh_helper: "SSH", @@ -68,7 +79,13 @@ export const AppLink: FC = ({ css={{ color: theme.palette.warning.light }} /> ); - primaryTooltip = "Unhealthy"; + + // Check if the unhealthy status is due to hostname length issues + if (hasHostnameLengthIssue(app)) { + primaryTooltip = "App name too long for DNS hostname. Please use a shorter app name, workspace name, or username."; + } else { + primaryTooltip = "Unhealthy"; + } } if (!host && app.subdomain) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy