feat: add sharing info to /workspaces endpoint (#21049)

closes: https://github.com/coder/internal/issues/858

Similar to https://github.com/coder/coder/pull/19375, this one uses
system permissions for fetching actual user and group data.

Modifies the `workspaces_expanded` view to fetch the required data; this way it's made available to all code paths that make use of it.  

Also fixes a bug in a test helper function that can result in `null` being saved to the DB for `user_acl` or `group_acl` and break tests; a defensive check constraint that prevents this is worth a PR, e.g:

`ALTER TABLE workspaces
   ADD CONSTRAINT group_acl_is_object CHECK (jsonb_typeof(group_acl) = 'object');`

Also adds missing  `OwnerName` in `ConvertWorkspaceRows`.
This commit is contained in:
George K
2025-12-15 08:42:08 -08:00
committed by GitHub
parent 7ecfd1aa07
commit 103967ed02
22 changed files with 838 additions and 92 deletions
+103 -34
View File
@@ -7749,6 +7749,52 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
| `max_token_lifetime` | integer | false | | |
| `refresh_default_duration` | integer | false | | Refresh default duration is the default lifetime for OAuth2 refresh tokens. This should generally be longer than access token lifetimes to allow refreshing after access token expiry. |
## codersdk.SharedWorkspaceActor
```json
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
|--------------|------------------------------------------------------------------------|----------|--------------|-------------|
| `actor_type` | [codersdk.SharedWorkspaceActorType](#codersdksharedworkspaceactortype) | false | | |
| `avatar_url` | string | false | | |
| `id` | string | false | | |
| `name` | string | false | | |
| `roles` | array of [codersdk.WorkspaceRole](#codersdkworkspacerole) | false | | |
#### Enumerated Values
| Property | Value |
|--------------|---------|
| `actor_type` | `group` |
| `actor_type` | `user` |
## codersdk.SharedWorkspaceActorType
```json
"group"
```
### Properties
#### Enumerated Values
| Value |
|---------|
| `group` |
| `user` |
## codersdk.SlimRole
```json
@@ -10354,6 +10400,17 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -10373,40 +10430,41 @@ If the schedule is empty, the user will be updated to use the default schedule.|
### Properties
| Name | Type | Required | Restrictions | Description |
|---------------------------------------------|------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allow_renames` | boolean | false | | |
| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | |
| `autostart_schedule` | string | false | | |
| `created_at` | string | false | | |
| `deleting_at` | string | false | | Deleting at indicates the time at which the workspace will be permanently deleted. A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value) and a value has been specified for time_til_dormant_autodelete on its template. |
| `dormant_at` | string | false | | Dormant at being non-nil indicates a workspace that is dormant. A dormant workspace is no longer accessible must be activated. It is subject to deletion if it breaches the duration of the time_til_ field on its template. |
| `favorite` | boolean | false | | |
| `health` | [codersdk.WorkspaceHealth](#codersdkworkspacehealth) | false | | Health shows the health of the workspace and information about what is causing an unhealthy status. |
| `id` | string | false | | |
| `is_prebuild` | boolean | false | | Is prebuild indicates whether the workspace is a prebuilt workspace. Prebuilt workspaces are owned by the prebuilds system user and have specific behavior, such as being managed differently from regular workspaces. Once a prebuilt workspace is claimed by a user, it transitions to a regular workspace, and IsPrebuild returns false. |
| `last_used_at` | string | false | | |
| `latest_app_status` | [codersdk.WorkspaceAppStatus](#codersdkworkspaceappstatus) | false | | |
| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | |
| `name` | string | false | | |
| `next_start_at` | string | false | | |
| `organization_id` | string | false | | |
| `organization_name` | string | false | | |
| `outdated` | boolean | false | | |
| `owner_avatar_url` | string | false | | |
| `owner_id` | string | false | | |
| `owner_name` | string | false | | Owner name is the username of the owner of the workspace. |
| `task_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Task ID if set, indicates that the workspace is relevant to the given codersdk.Task. |
| `template_active_version_id` | string | false | | |
| `template_allow_user_cancel_workspace_jobs` | boolean | false | | |
| `template_display_name` | string | false | | |
| `template_icon` | string | false | | |
| `template_id` | string | false | | |
| `template_name` | string | false | | |
| `template_require_active_version` | boolean | false | | |
| `template_use_classic_parameter_flow` | boolean | false | | |
| `ttl_ms` | integer | false | | |
| `updated_at` | string | false | | |
| Name | Type | Required | Restrictions | Description |
|---------------------------------------------|-------------------------------------------------------------------------|----------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allow_renames` | boolean | false | | |
| `automatic_updates` | [codersdk.AutomaticUpdates](#codersdkautomaticupdates) | false | | |
| `autostart_schedule` | string | false | | |
| `created_at` | string | false | | |
| `deleting_at` | string | false | | Deleting at indicates the time at which the workspace will be permanently deleted. A workspace is eligible for deletion if it is dormant (a non-nil dormant_at value) and a value has been specified for time_til_dormant_autodelete on its template. |
| `dormant_at` | string | false | | Dormant at being non-nil indicates a workspace that is dormant. A dormant workspace is no longer accessible must be activated. It is subject to deletion if it breaches the duration of the time_til_ field on its template. |
| `favorite` | boolean | false | | |
| `health` | [codersdk.WorkspaceHealth](#codersdkworkspacehealth) | false | | Health shows the health of the workspace and information about what is causing an unhealthy status. |
| `id` | string | false | | |
| `is_prebuild` | boolean | false | | Is prebuild indicates whether the workspace is a prebuilt workspace. Prebuilt workspaces are owned by the prebuilds system user and have specific behavior, such as being managed differently from regular workspaces. Once a prebuilt workspace is claimed by a user, it transitions to a regular workspace, and IsPrebuild returns false. |
| `last_used_at` | string | false | | |
| `latest_app_status` | [codersdk.WorkspaceAppStatus](#codersdkworkspaceappstatus) | false | | |
| `latest_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | |
| `name` | string | false | | |
| `next_start_at` | string | false | | |
| `organization_id` | string | false | | |
| `organization_name` | string | false | | |
| `outdated` | boolean | false | | |
| `owner_avatar_url` | string | false | | |
| `owner_id` | string | false | | |
| `owner_name` | string | false | | Owner name is the username of the owner of the workspace. |
| `shared_with` | array of [codersdk.SharedWorkspaceActor](#codersdksharedworkspaceactor) | false | | |
| `task_id` | [uuid.NullUUID](#uuidnulluuid) | false | | Task ID if set, indicates that the workspace is relevant to the given codersdk.Task. |
| `template_active_version_id` | string | false | | |
| `template_allow_user_cancel_workspace_jobs` | boolean | false | | |
| `template_display_name` | string | false | | |
| `template_icon` | string | false | | |
| `template_id` | string | false | | |
| `template_name` | string | false | | |
| `template_require_active_version` | boolean | false | | |
| `template_use_classic_parameter_flow` | boolean | false | | |
| `ttl_ms` | integer | false | | |
| `updated_at` | string | false | | |
#### Enumerated Values
@@ -12347,6 +12405,17 @@ If the schedule is empty, the user will be updated to use the default schedule.|
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
+66
View File
@@ -295,6 +295,17 @@ of the template will be used.
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -589,6 +600,17 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -908,6 +930,17 @@ of the template will be used.
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -1188,6 +1221,17 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -1483,6 +1527,17 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true
@@ -2037,6 +2092,17 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \
"owner_avatar_url": "string",
"owner_id": "8826ee2e-7933-4665-aef2-2393f84a0d05",
"owner_name": "string",
"shared_with": [
{
"actor_type": "group",
"avatar_url": "http://example.com",
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"roles": [
"admin"
]
}
],
"task_id": {
"uuid": "string",
"valid": true