From d726fab603ddcfa9c392b8f1e7c1c236899cbe92 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 12:07:26 +0200 Subject: [PATCH 01/12] WIP --- coderd/apidoc/docs.go | 4 ++++ coderd/apidoc/swagger.json | 4 ++++ codersdk/workspaceapps.go | 6 ++++-- docs/api/agents.md | 2 ++ docs/api/builds.md | 8 ++++++++ docs/api/schemas.md | 34 +++++++++++++++++++++------------- docs/api/templates.md | 4 ++++ docs/api/workspaces.md | 5 +++++ site/src/api/typesGenerated.ts | 1 + 9 files changed, 53 insertions(+), 15 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 2c45b383cc90b..d96f03b6cf25a 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -10970,6 +10970,10 @@ const docTemplate = `{ "description": "Subdomain denotes whether the app should be accessed via a path on the\n` + "`" + `coder server` + "`" + ` or via a hostname-based dev URL. If this is set to true\nand there is no app wildcard configured on the server, the app will not\nbe accessible in the UI.", "type": "boolean" }, + "subdomain_name": { + "description": "SubdomainName is the application domain exposed on the ` + "`" + `coder server` + "`" + `.", + "type": "string" + }, "url": { "description": "URL is the address being proxied to inside the workspace.\nIf external is specified, this will be opened on the client.", "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 21fbd82b348ca..99582f5665b2a 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9961,6 +9961,10 @@ "description": "Subdomain denotes whether the app should be accessed via a path on the\n`coder server` or via a hostname-based dev URL. If this is set to true\nand there is no app wildcard configured on the server, the app will not\nbe accessible in the UI.", "type": "boolean" }, + "subdomain_name": { + "description": "SubdomainName is the application domain exposed on the `coder server`.", + "type": "string" + }, "url": { "description": "URL is the address being proxied to inside the workspace.\nIf external is specified, this will be opened on the client.", "type": "string" diff --git a/codersdk/workspaceapps.go b/codersdk/workspaceapps.go index ba2e7255888cc..9c8d89b42f65a 100644 --- a/codersdk/workspaceapps.go +++ b/codersdk/workspaceapps.go @@ -41,8 +41,10 @@ type WorkspaceApp struct { // `coder server` or via a hostname-based dev URL. If this is set to true // and there is no app wildcard configured on the server, the app will not // be accessible in the UI. - Subdomain bool `json:"subdomain"` - SharingLevel WorkspaceAppSharingLevel `json:"sharing_level" enums:"owner,authenticated,public"` + Subdomain bool `json:"subdomain"` + // SubdomainName is the application domain exposed on the `coder server`. + SubdomainName string `json:"subdomain_name,omitempty"` + SharingLevel WorkspaceAppSharingLevel `json:"sharing_level" enums:"owner,authenticated,public"` // Healthcheck specifies the configuration for checking app health. Healthcheck Healthcheck `json:"healthcheck"` Health WorkspaceAppHealth `json:"health"` diff --git a/docs/api/agents.md b/docs/api/agents.md index 7bd27794bb03f..5edaeb5604ebc 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -389,6 +389,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/manifest \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -658,6 +659,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], diff --git a/docs/api/builds.md b/docs/api/builds.md index 864b1e8775b0a..f81f60d605768 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -74,6 +74,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -237,6 +238,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -539,6 +541,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -640,6 +643,7 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | @@ -798,6 +802,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -966,6 +971,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -1103,6 +1109,7 @@ Status Code **200** | `»»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `»»»» subdomain_name` | string | false | | »»»subdomain name is the application domain exposed on the `coder server`. | | `»»»» url` | string | false | | »»»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | | `»»» architecture` | string | false | | | | `»»» connection_timeout_seconds` | integer | false | | | @@ -1314,6 +1321,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 4cf60a773f81d..5fb5a846cb86f 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -198,6 +198,7 @@ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -5438,6 +5439,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -5581,6 +5583,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -5939,25 +5942,27 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------- | ---------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `command` | string | false | | | -| `display_name` | string | false | | Display name is a friendly name for the app. | -| `external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. | -| `health` | [codersdk.WorkspaceAppHealth](#codersdkworkspaceapphealth) | false | | | -| `healthcheck` | [codersdk.Healthcheck](#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. | -| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | -| `id` | string | false | | | -| `sharing_level` | [codersdk.WorkspaceAppSharingLevel](#codersdkworkspaceappsharinglevel) | false | | | -| `slug` | string | false | | Slug is a unique identifier within the agent. | -| `subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -| `url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | +| Name | Type | Required | Restrictions | Description | +| ---------------- | ---------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `command` | string | false | | | +| `display_name` | string | false | | Display name is a friendly name for the app. | +| `external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. | +| `health` | [codersdk.WorkspaceAppHealth](#codersdkworkspaceapphealth) | false | | | +| `healthcheck` | [codersdk.Healthcheck](#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. | +| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. | +| `id` | string | false | | | +| `sharing_level` | [codersdk.WorkspaceAppSharingLevel](#codersdkworkspaceappsharinglevel) | false | | | +| `slug` | string | false | | Slug is a unique identifier within the agent. | +| `subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | #### Enumerated Values @@ -6051,6 +6056,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -6363,6 +6369,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -6577,6 +6584,7 @@ If the schedule is empty, the user will be updated to use the default schedule.| "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], diff --git a/docs/api/templates.md b/docs/api/templates.md index 723cc9039d9eb..aa228e0488264 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1585,6 +1585,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -1686,6 +1687,7 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | @@ -1978,6 +1980,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -2079,6 +2082,7 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | +| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index fd9bb1c817a83..f11f2bda3973e 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -104,6 +104,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -294,6 +295,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -483,6 +485,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -674,6 +677,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], @@ -944,6 +948,7 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ "sharing_level": "owner", "slug": "string", "subdomain": true, + "subdomain_name": "string", "url": "string" } ], diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f7511276a081e..90398957fa91c 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1416,6 +1416,7 @@ export interface WorkspaceApp { readonly command?: string; readonly icon?: string; readonly subdomain: boolean; + readonly subdomain_name?: string; readonly sharing_level: WorkspaceAppSharingLevel; readonly healthcheck: Healthcheck; readonly health: WorkspaceAppHealth; From ec977f234921fd5f9669352120aedbd34c2e5662 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 12:25:55 +0200 Subject: [PATCH 02/12] make gen --- docs/api/builds.md | 12 ++---------- docs/api/templates.md | 12 ++---------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/docs/api/builds.md b/docs/api/builds.md index f44c110510263..46a825d92c0bc 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -643,12 +643,8 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -<<<<<<< HEAD -| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | -| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | -======= +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | ->>>>>>> main | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | | `»» created_at` | string(date-time) | false | | | @@ -1113,12 +1109,8 @@ Status Code **200** | `»»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -<<<<<<< HEAD -| `»»»» subdomain_name` | string | false | | »»»subdomain name is the application domain exposed on the `coder server`. | -| `»»»» url` | string | false | | »»»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | -======= +| `»»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | | `»»»» url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | ->>>>>>> main | `»»» architecture` | string | false | | | | `»»» connection_timeout_seconds` | integer | false | | | | `»»» created_at` | string(date-time) | false | | | diff --git a/docs/api/templates.md b/docs/api/templates.md index aefb84f986b9d..2e5eac3abe7f2 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1687,12 +1687,8 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -<<<<<<< HEAD -| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | -| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | -======= +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | ->>>>>>> main | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | | `»» created_at` | string(date-time) | false | | | @@ -2086,12 +2082,8 @@ Status Code **200** | `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | | | `»»» slug` | string | false | | Slug is a unique identifier within the agent. | | `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. | -<<<<<<< HEAD -| `»»» subdomain_name` | string | false | | »»subdomain name is the application domain exposed on the `coder server`. | -| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | -======= +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | | `»»» url` | string | false | | URL is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. | ->>>>>>> main | `»» architecture` | string | false | | | | `»» connection_timeout_seconds` | integer | false | | | | `»» created_at` | string(date-time) | false | | | From e914801bd02c7e82b7529e4986237c09160119bc Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 13:24:51 +0200 Subject: [PATCH 03/12] convertApps --- coderd/provisionerjobs.go | 2 +- coderd/workspaceagents.go | 46 ++++++++++++++++++++++++++++++++++++--- coderd/workspacebuilds.go | 2 +- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 4b49c385c80f4..22218aaf83cc4 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -149,7 +149,7 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, } apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertProvisionedApps(dbApps), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index acff82c7f135d..8209b45179e36 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -64,8 +64,42 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { }) return } + + resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace resource.", + Detail: err.Error(), + }) + return + } + build, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace build.", + Detail: err.Error(), + }) + return + } + workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace.", + Detail: err.Error(), + }) + return + } + owner, err := api.Database.GetUserByID(ctx, workspace.OwnerID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace owner.", + Detail: err.Error(), + }) + return + } + apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps, workspaceAgent, owner, workspace), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -165,7 +199,7 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Manifest{ AgentID: apiAgent.ID, - Apps: convertApps(dbApps), + Apps: convertApps(dbApps, workspaceAgent, owner, workspace), DERPMap: api.DERPMap(), DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), GitAuthConfigs: len(api.GitAuthConfigs), @@ -1281,7 +1315,13 @@ func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.R } } -func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { +// convertProvisionedApps converts applications that are in the middle of provisioning process. +// It means that they may not have an agent or workspace assigned (dry-run job). +func convertProvisionedApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { + return convertApps(dbApps, database.WorkspaceAgent{}, database.User{}, database.Workspace{}) +} + +func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, owner database.User, workspace database.Workspace) []codersdk.WorkspaceApp { apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { apps = append(apps, codersdk.WorkspaceApp{ diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 99bf36a33313e..6081d07580c61 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -832,7 +832,7 @@ func (api *API) convertWorkspaceBuild( for _, agent := range agents { apps := appsByAgentID[agent.ID] apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(apps), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(apps, agent, owner, workspace), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { From 55d7ff1000e662aaa527686904139e3b978d1754 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 13:39:14 +0200 Subject: [PATCH 04/12] storybook --- coderd/workspaceagents.go | 19 +++++++++++++++ .../Resources/AppLink/AppLink.stories.tsx | 23 +++++++++++++++---- .../components/Resources/AppLink/AppLink.tsx | 8 ++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 8209b45179e36..b3dbfc7874c5c 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1324,6 +1324,25 @@ func convertProvisionedApps(dbApps []database.WorkspaceApp) []codersdk.Workspace func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, owner database.User, workspace database.Workspace) []codersdk.WorkspaceApp { apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { + /* + let appSlug = app.slug; + let appDisplayName = app.display_name; + if (!appSlug) { + appSlug = appDisplayName; + } + if (!appDisplayName) { + appDisplayName = appSlug; + } + + + + if (appsHost && app.subdomain) { + const subdomain = `${appSlug}--${agent.name}--${workspace.name}--${username}`; + href = `${window.location.protocol}//${appsHost}/`.replace("*", subdomain); + } + + */ + apps = append(apps, codersdk.WorkspaceApp{ ID: dbApp.ID, URL: dbApp.Url.String, diff --git a/site/src/components/Resources/AppLink/AppLink.stories.tsx b/site/src/components/Resources/AppLink/AppLink.stories.tsx index 9229ca8f1762b..814b29a37323a 100644 --- a/site/src/components/Resources/AppLink/AppLink.stories.tsx +++ b/site/src/components/Resources/AppLink/AppLink.stories.tsx @@ -18,10 +18,13 @@ const meta: Meta = { = ({ app, workspace, agent }) => { }/terminal?command=${encodeURIComponent(app.command)}`; } - if (appsHost && app.subdomain) { - const subdomain = `${appSlug}--${agent.name}--${workspace.name}--${username}`; - href = `${window.location.protocol}//${appsHost}/`.replace("*", subdomain); + if (appsHost && app.subdomain && app.subdomain_name) { + href = `${window.location.protocol}//${appsHost}/`.replace( + "*", + app.subdomain_name, + ); } if (app.external) { href = app.url; From f56dbe360b15d2814059b149644dd0ee9e729065 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 13:40:26 +0200 Subject: [PATCH 05/12] storybook --- site/src/components/Resources/AppLink/AppLink.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/site/src/components/Resources/AppLink/AppLink.stories.tsx b/site/src/components/Resources/AppLink/AppLink.stories.tsx index 814b29a37323a..4e71c244b5e63 100644 --- a/site/src/components/Resources/AppLink/AppLink.stories.tsx +++ b/site/src/components/Resources/AppLink/AppLink.stories.tsx @@ -144,6 +144,7 @@ export const InternalApp: Story = { workspace: MockWorkspace, app: { ...MockWorkspaceApp, + display_name: "Check my URL", subdomain: true, subdomain_name: "slug--agent_name--workspace_name--username", }, From 826ef7603f32dbcdb757cf7979c262e663cfb045 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 14:22:30 +0200 Subject: [PATCH 06/12] use app url --- coderd/workspaceagents.go | 50 ++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index b3dbfc7874c5c..c1034ab25dc5e 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1324,35 +1324,31 @@ func convertProvisionedApps(dbApps []database.WorkspaceApp) []codersdk.Workspace func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, owner database.User, workspace database.Workspace) []codersdk.WorkspaceApp { apps := make([]codersdk.WorkspaceApp, 0) for _, dbApp := range dbApps { - /* - let appSlug = app.slug; - let appDisplayName = app.display_name; - if (!appSlug) { - appSlug = appDisplayName; - } - if (!appDisplayName) { - appDisplayName = appSlug; - } - - - - if (appsHost && app.subdomain) { - const subdomain = `${appSlug}--${agent.name}--${workspace.name}--${username}`; - href = `${window.location.protocol}//${appsHost}/`.replace("*", subdomain); - } - - */ + var subdomainName string + if dbApp.Subdomain && agent.Name != "" && owner.Username != "" && workspace.Name != "" { + var appSlug = dbApp.Slug + if appSlug == "" { + appSlug = dbApp.DisplayName + } + subdomainName = httpapi.ApplicationURL{ + AppSlugOrPort: appSlug, + AgentName: agent.Name, + WorkspaceName: workspace.Name, + Username: owner.Username, + }.String() + } apps = append(apps, codersdk.WorkspaceApp{ - ID: dbApp.ID, - URL: dbApp.Url.String, - External: dbApp.External, - Slug: dbApp.Slug, - DisplayName: dbApp.DisplayName, - Command: dbApp.Command.String, - Icon: dbApp.Icon, - Subdomain: dbApp.Subdomain, - SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel), + ID: dbApp.ID, + URL: dbApp.Url.String, + External: dbApp.External, + Slug: dbApp.Slug, + DisplayName: dbApp.DisplayName, + Command: dbApp.Command.String, + Icon: dbApp.Icon, + Subdomain: dbApp.Subdomain, + SubdomainName: subdomainName, + SharingLevel: codersdk.WorkspaceAppSharingLevel(dbApp.SharingLevel), Healthcheck: codersdk.Healthcheck{ URL: dbApp.HealthcheckUrl, Interval: dbApp.HealthcheckInterval, From d998238ac50a153f3baf5186d299bc068c837c93 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 11 Sep 2023 14:24:46 +0200 Subject: [PATCH 07/12] make fmt --- coderd/workspaceagents.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index c1034ab25dc5e..789ce0a74244d 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -1326,7 +1326,7 @@ func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, for _, dbApp := range dbApps { var subdomainName string if dbApp.Subdomain && agent.Name != "" && owner.Username != "" && workspace.Name != "" { - var appSlug = dbApp.Slug + appSlug := dbApp.Slug if appSlug == "" { appSlug = dbApp.DisplayName } From 41459dd0cf684eb70cba6c12d0986d73c0413a63 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 12 Sep 2023 10:17:13 +0200 Subject: [PATCH 08/12] test --- coderd/workspaceapps/apptest/setup.go | 74 +++++++++++++++++---------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index 4dc8cd57eebca..07a3855885bfb 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -291,6 +291,37 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U } appURL := fmt.Sprintf("%s://127.0.0.1:%d?%s", scheme, port, proxyTestAppQuery) + protoApps := []*proto.App{ + { + Slug: proxyTestAppNameFake, + DisplayName: proxyTestAppNameFake, + SharingLevel: proto.AppSharingLevel_OWNER, + // Hopefully this IP and port doesn't exist. + Url: "http://127.1.0.1:65535", + Subdomain: true, + }, + { + Slug: proxyTestAppNameOwner, + DisplayName: proxyTestAppNameOwner, + SharingLevel: proto.AppSharingLevel_OWNER, + Url: appURL, + Subdomain: true, + }, + { + Slug: proxyTestAppNameAuthenticated, + DisplayName: proxyTestAppNameAuthenticated, + SharingLevel: proto.AppSharingLevel_AUTHENTICATED, + Url: appURL, + Subdomain: true, + }, + { + Slug: proxyTestAppNamePublic, + DisplayName: proxyTestAppNamePublic, + SharingLevel: proto.AppSharingLevel_PUBLIC, + Url: appURL, + Subdomain: true, + }, + } version := coderdtest.CreateTemplateVersion(t, client, orgID, &echo.Responses{ Parse: echo.ParseComplete, ProvisionPlan: echo.PlanComplete, @@ -306,33 +337,7 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U Auth: &proto.Agent_Token{ Token: authToken, }, - Apps: []*proto.App{ - { - Slug: proxyTestAppNameFake, - DisplayName: proxyTestAppNameFake, - SharingLevel: proto.AppSharingLevel_OWNER, - // Hopefully this IP and port doesn't exist. - Url: "http://127.1.0.1:65535", - }, - { - Slug: proxyTestAppNameOwner, - DisplayName: proxyTestAppNameOwner, - SharingLevel: proto.AppSharingLevel_OWNER, - Url: appURL, - }, - { - Slug: proxyTestAppNameAuthenticated, - DisplayName: proxyTestAppNameAuthenticated, - SharingLevel: proto.AppSharingLevel_AUTHENTICATED, - Url: appURL, - }, - { - Slug: proxyTestAppNamePublic, - DisplayName: proxyTestAppNamePublic, - SharingLevel: proto.AppSharingLevel_PUBLIC, - Url: appURL, - }, - }, + Apps: protoApps, }}, }}, }, @@ -342,7 +347,20 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U template := coderdtest.CreateTemplate(t, client, orgID, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) workspace := coderdtest.CreateWorkspace(t, client, orgID, template.ID, workspaceMutators...) - coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + // Verify app subdomains + for i, app := range workspaceBuild.Resources[0].Agents[0].Apps { + require.True(t, app.Subdomain) + + appURL := httpapi.ApplicationURL{ + AppSlugOrPort: protoApps[i].Slug, + AgentName: proxyTestAgentName, + WorkspaceName: workspace.Name, + Username: me.Username, + } + require.Equal(t, appURL.String(), app.SubdomainName) + } agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) From 74d9dbe3e37a1e1ff676d26ca576b287fc929812 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 12 Sep 2023 12:42:48 +0200 Subject: [PATCH 09/12] fix --- coderd/workspaceapps/apptest/setup.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index 07a3855885bfb..b58b714a21613 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -350,11 +350,13 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U workspaceBuild := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) // Verify app subdomains - for i, app := range workspaceBuild.Resources[0].Agents[0].Apps { + for _, app := range workspaceBuild.Resources[0].Agents[0].Apps { require.True(t, app.Subdomain) appURL := httpapi.ApplicationURL{ - AppSlugOrPort: protoApps[i].Slug, + // findProtoApp is needed as the order of apps returned from PG database + // is not guaranteed. + AppSlugOrPort: findProtoApp(t, protoApps, app.Slug).Slug, AgentName: proxyTestAgentName, WorkspaceName: workspace.Name, Username: me.Username, @@ -406,6 +408,16 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U return workspace, agents[0] } +func findProtoApp(t *testing.T, protoApps []*proto.App, slug string) proto.App { + for _, protoApp := range protoApps { + if protoApp.Slug == slug { + return *protoApp + } + } + require.FailNowf(t, "proto app not found (slug: %q)", slug) + return proto.App{} +} + func doWithRetries(t require.TestingT, client *codersdk.Client, req *http.Request) (*http.Response, error) { var resp *http.Response var err error From b0b2c33e70a4aee179aefdc3e5987b34159480ff Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 12 Sep 2023 12:53:17 +0200 Subject: [PATCH 10/12] fix --- coderd/workspaceapps/apptest/setup.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/workspaceapps/apptest/setup.go b/coderd/workspaceapps/apptest/setup.go index b58b714a21613..3175f311dbccd 100644 --- a/coderd/workspaceapps/apptest/setup.go +++ b/coderd/workspaceapps/apptest/setup.go @@ -408,14 +408,14 @@ func createWorkspaceWithApps(t *testing.T, client *codersdk.Client, orgID uuid.U return workspace, agents[0] } -func findProtoApp(t *testing.T, protoApps []*proto.App, slug string) proto.App { +func findProtoApp(t *testing.T, protoApps []*proto.App, slug string) *proto.App { for _, protoApp := range protoApps { if protoApp.Slug == slug { - return *protoApp + return protoApp } } require.FailNowf(t, "proto app not found (slug: %q)", slug) - return proto.App{} + return nil } func doWithRetries(t require.TestingT, client *codersdk.Client, req *http.Request) (*http.Response, error) { From da4ddde396031ae1bdbf10944279bfad3bde3c2b Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 12 Sep 2023 14:31:58 +0200 Subject: [PATCH 11/12] WIP --- .../components/Resources/AppLink/AppLink.tsx | 31 +++++++----------- site/src/utils/apps.test.ts | 3 ++ site/src/utils/apps.ts | 32 +++++++++++++++++++ 3 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 site/src/utils/apps.test.ts create mode 100644 site/src/utils/apps.ts diff --git a/site/src/components/Resources/AppLink/AppLink.tsx b/site/src/components/Resources/AppLink/AppLink.tsx index 7b12de159307a..9075fec95928a 100644 --- a/site/src/components/Resources/AppLink/AppLink.tsx +++ b/site/src/components/Resources/AppLink/AppLink.tsx @@ -11,6 +11,7 @@ import { generateRandomString } from "../../../utils/random"; import { BaseIcon } from "./BaseIcon"; import { ShareIcon } from "./ShareIcon"; import { useProxy } from "contexts/ProxyContext"; +import { createAppLinkHref } from "utils/apps"; const Language = { appTitle: (appName: string, identifier: string): string => @@ -40,26 +41,16 @@ export const AppLink: FC = ({ app, workspace, agent }) => { appDisplayName = appSlug; } - // The backend redirects if the trailing slash isn't included, so we add it - // here to avoid extra roundtrips. - let href = `${preferredPathBase}/@${username}/${workspace.name}.${ - agent.name - }/apps/${encodeURIComponent(appSlug)}/`; - if (app.command) { - href = `${preferredPathBase}/@${username}/${workspace.name}.${ - agent.name - }/terminal?command=${encodeURIComponent(app.command)}`; - } - - if (appsHost && app.subdomain && app.subdomain_name) { - href = `${window.location.protocol}//${appsHost}/`.replace( - "*", - app.subdomain_name, - ); - } - if (app.external) { - href = app.url; - } + const href = createAppLinkHref( + window.location.protocol, + preferredPathBase, + appsHost, + appSlug, + username, + workspace, + agent, + app, + ); let canClick = true; let icon = ; diff --git a/site/src/utils/apps.test.ts b/site/src/utils/apps.test.ts new file mode 100644 index 0000000000000..d3b8956a0354a --- /dev/null +++ b/site/src/utils/apps.test.ts @@ -0,0 +1,3 @@ +describe("create app link", () => { + it("with external URL", () => {}); +}); diff --git a/site/src/utils/apps.ts b/site/src/utils/apps.ts new file mode 100644 index 0000000000000..fafa17ac82959 --- /dev/null +++ b/site/src/utils/apps.ts @@ -0,0 +1,32 @@ +import * as TypesGen from "../api/typesGenerated"; + +export const createAppLinkHref = ( + protocol: string, + preferredPathBase: string, + appsHost: string, + appSlug: string, + username: string, + workspace: TypesGen.Workspace, + agent: TypesGen.WorkspaceAgent, + app: TypesGen.WorkspaceApp, +): string => { + if (app.external) { + return app.url; + } + + // The backend redirects if the trailing slash isn't included, so we add it + // here to avoid extra roundtrips. + let href = `${preferredPathBase}/@${username}/${workspace.name}.${ + agent.name + }/apps/${encodeURIComponent(appSlug)}/`; + if (app.command) { + href = `${preferredPathBase}/@${username}/${workspace.name}.${ + agent.name + }/terminal?command=${encodeURIComponent(app.command)}`; + } + + if (appsHost && app.subdomain && app.subdomain_name) { + href = `${protocol}//${appsHost}/`.replace("*", app.subdomain_name); + } + return href; +}; From fea0af326480c242ba3e457736a156fd94da6369 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 12 Sep 2023 14:43:32 +0200 Subject: [PATCH 12/12] unit tests --- site/src/utils/apps.test.ts | 102 +++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/site/src/utils/apps.test.ts b/site/src/utils/apps.test.ts index d3b8956a0354a..9e188efb2af35 100644 --- a/site/src/utils/apps.test.ts +++ b/site/src/utils/apps.test.ts @@ -1,3 +1,103 @@ +import { createAppLinkHref } from "./apps"; +import { + MockWorkspace, + MockWorkspaceAgent, + MockWorkspaceApp, +} from "testHelpers/entities"; + describe("create app link", () => { - it("with external URL", () => {}); + it("with external URL", () => { + const externalURL = "https://external-url.tld"; + const href = createAppLinkHref( + "http:", + "/path-base", + "*.apps-host.tld", + "app-slug", + "username", + MockWorkspace, + MockWorkspaceAgent, + { + ...MockWorkspaceApp, + external: true, + url: externalURL, + }, + ); + expect(href).toBe(externalURL); + }); + + it("without subdomain", () => { + const href = createAppLinkHref( + "http:", + "/path-base", + "*.apps-host.tld", + "app-slug", + "username", + MockWorkspace, + MockWorkspaceAgent, + { + ...MockWorkspaceApp, + subdomain: false, + }, + ); + expect(href).toBe( + "/path-base/@username/Test-Workspace.a-workspace-agent/apps/app-slug/", + ); + }); + + it("with command", () => { + const href = createAppLinkHref( + "https:", + "/path-base", + "*.apps-host.tld", + "app-slug", + "username", + MockWorkspace, + MockWorkspaceAgent, + { + ...MockWorkspaceApp, + command: "ls -la", + }, + ); + expect(href).toBe( + "/path-base/@username/Test-Workspace.a-workspace-agent/terminal?command=ls%20-la", + ); + }); + + it("with subdomain", () => { + const href = createAppLinkHref( + "ftps:", + "/path-base", + "*.apps-host.tld", + "app-slug", + "username", + MockWorkspace, + MockWorkspaceAgent, + { + ...MockWorkspaceApp, + subdomain: true, + subdomain_name: "hellocoder", + }, + ); + expect(href).toBe("ftps://hellocoder.apps-host.tld/"); + }); + + it("with subdomain, but not apps host", () => { + const href = createAppLinkHref( + "ftps:", + "/path-base", + "", + "app-slug", + "username", + MockWorkspace, + MockWorkspaceAgent, + { + ...MockWorkspaceApp, + subdomain: true, + subdomain_name: "hellocoder", + }, + ); + expect(href).toBe( + "/path-base/@username/Test-Workspace.a-workspace-agent/apps/app-slug/", + ); + }); });