chore: move Shared Workspaces from experiments to beta (#22206)

* Removed the shared-workspaces experiment and cleaned up related
middleware
* Added beta tagging to the UI for shared workspaces
This commit is contained in:
Sushant P
2026-02-23 08:30:32 -08:00
committed by GitHub
parent 4d84d42e02
commit 37a8e61ea2
23 changed files with 56 additions and 194 deletions
+1 -5
View File
@@ -106,11 +106,7 @@ func TestList(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
memberClient, member = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
sharedWorkspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
+7 -31
View File
@@ -25,11 +25,7 @@ func TestSharingShare(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -68,12 +64,8 @@ func TestSharingShare(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
orgOwner = coderdtest.CreateFirstUser(t, client)
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -127,11 +119,7 @@ func TestSharingShare(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -182,11 +170,7 @@ func TestSharingStatus(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -230,11 +214,7 @@ func TestSharingRemove(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
@@ -291,11 +271,7 @@ func TestSharingRemove(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
+3 -4
View File
@@ -49,10 +49,9 @@ OPTIONS:
security purposes if a --wildcard-access-url is configured.
--disable-workspace-sharing bool, $CODER_DISABLE_WORKSPACE_SHARING
Disable workspace sharing (requires the "workspace-sharing" experiment
to be enabled). Workspace ACL checking is disabled and only owners can
have ssh, apps and terminal access to workspaces. Access based on the
'owner' role is also allowed unless disabled via
Disable workspace sharing. Workspace ACL checking is disabled and only
owners can have ssh, apps and terminal access to workspaces. Access
based on the 'owner' role is also allowed unless disabled via
--disable-owner-workspace-access.
--swagger-enable bool, $CODER_SWAGGER_ENABLE
+4 -4
View File
@@ -523,10 +523,10 @@ disablePathApps: false
# workspaces.
# (default: <unset>, type: bool)
disableOwnerWorkspaceAccess: false
# Disable workspace sharing (requires the "workspace-sharing" experiment to be
# enabled). Workspace ACL checking is disabled and only owners can have ssh, apps
# and terminal access to workspaces. Access based on the 'owner' role is also
# allowed unless disabled via --disable-owner-workspace-access.
# Disable workspace sharing. Workspace ACL checking is disabled and only owners
# can have ssh, apps and terminal access to workspaces. Access based on the
# 'owner' role is also allowed unless disabled via
# --disable-owner-workspace-access.
# (default: <unset>, type: bool)
disableWorkspaceSharing: false
# These options change the behavior of how clients interact with the Coder.
-2
View File
@@ -466,7 +466,6 @@ func (api *API) convertTasks(ctx context.Context, requesterID uuid.UUID, dbTasks
apiWorkspaces, err := convertWorkspaces(
ctx,
api.Experiments,
api.Logger,
requesterID,
workspaces,
@@ -546,7 +545,6 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
ws, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspace,
+3 -7
View File
@@ -15097,8 +15097,7 @@ const docTemplate = `{
"workspace-usage",
"web-push",
"oauth2",
"mcp-server-http",
"workspace-sharing"
"mcp-server-http"
],
"x-enum-comments": {
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
@@ -15107,7 +15106,6 @@ const docTemplate = `{
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
"ExperimentOAuth2": "Enables OAuth2 provider functionality.",
"ExperimentWebPush": "Enables web push notifications through the browser.",
"ExperimentWorkspaceSharing": "Enables updating workspace ACLs for sharing with users and groups.",
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
},
"x-enum-descriptions": [
@@ -15117,8 +15115,7 @@ const docTemplate = `{
"Enables the new workspace usage tracking.",
"Enables web push notifications through the browser.",
"Enables OAuth2 provider functionality.",
"Enables the MCP HTTP server functionality.",
"Enables updating workspace ACLs for sharing with users and groups."
"Enables the MCP HTTP server functionality."
],
"x-enum-varnames": [
"ExperimentExample",
@@ -15127,8 +15124,7 @@ const docTemplate = `{
"ExperimentWorkspaceUsage",
"ExperimentWebPush",
"ExperimentOAuth2",
"ExperimentMCPServerHTTP",
"ExperimentWorkspaceSharing"
"ExperimentMCPServerHTTP"
]
},
"codersdk.ExternalAPIKeyScopes": {
+3 -7
View File
@@ -13624,8 +13624,7 @@
"workspace-usage",
"web-push",
"oauth2",
"mcp-server-http",
"workspace-sharing"
"mcp-server-http"
],
"x-enum-comments": {
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
@@ -13634,7 +13633,6 @@
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
"ExperimentOAuth2": "Enables OAuth2 provider functionality.",
"ExperimentWebPush": "Enables web push notifications through the browser.",
"ExperimentWorkspaceSharing": "Enables updating workspace ACLs for sharing with users and groups.",
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
},
"x-enum-descriptions": [
@@ -13644,8 +13642,7 @@
"Enables the new workspace usage tracking.",
"Enables web push notifications through the browser.",
"Enables OAuth2 provider functionality.",
"Enables the MCP HTTP server functionality.",
"Enables updating workspace ACLs for sharing with users and groups."
"Enables the MCP HTTP server functionality."
],
"x-enum-varnames": [
"ExperimentExample",
@@ -13654,8 +13651,7 @@
"ExperimentWorkspaceUsage",
"ExperimentWebPush",
"ExperimentOAuth2",
"ExperimentMCPServerHTTP",
"ExperimentWorkspaceSharing"
"ExperimentMCPServerHTTP"
]
},
"codersdk.ExternalAPIKeyScopes": {
-4
View File
@@ -1526,10 +1526,6 @@ func New(options *Options) *API {
})
r.Get("/timings", api.workspaceTimings)
r.Route("/acl", func(r chi.Router) {
r.Use(
httpmw.RequireExperiment(api.Experiments, codersdk.ExperimentWorkspaceSharing),
)
r.Get("/", api.workspaceACL)
r.Patch("/", api.patchWorkspaceACL)
r.Delete("/", api.deleteWorkspaceACL)
+2 -17
View File
@@ -114,7 +114,6 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
w, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspace,
@@ -240,7 +239,6 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
wss, err := convertWorkspaces(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspaces,
@@ -336,7 +334,6 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
w, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspace,
@@ -868,7 +865,6 @@ func createWorkspace(
w, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
initiatorID,
workspace,
@@ -1514,7 +1510,6 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
w, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspace,
@@ -2094,7 +2089,6 @@ func (api *API) watchWorkspace(
}
w, err := convertWorkspace(
ctx,
api.Experiments,
api.Logger,
apiKey.UserID,
workspace,
@@ -2236,8 +2230,7 @@ func (api *API) workspaceACL(rw http.ResponseWriter, r *http.Request) {
// the case here. This data goes directly to an unauthorized user. We are
// just straight up breaking security promises.
//
// Fine for now while behind the shared-workspaces experiment, but needs to
// be fixed before GA.
// TODO: This needs to be fixed before GA. Currently in beta.
// Fetch all of the users and their organization memberships
userIDs := make([]uuid.UUID, 0, len(workspaceACL.Users))
@@ -2595,7 +2588,6 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa
func convertWorkspaces(
ctx context.Context,
experiments codersdk.Experiments,
logger slog.Logger,
requesterID uuid.UUID,
workspaces []database.Workspace,
@@ -2633,7 +2625,6 @@ func convertWorkspaces(
w, err := convertWorkspace(
ctx,
experiments,
logger,
requesterID,
workspace,
@@ -2653,7 +2644,6 @@ func convertWorkspaces(
func convertWorkspace(
ctx context.Context,
experiments codersdk.Experiments,
logger slog.Logger,
requesterID uuid.UUID,
workspace database.Workspace,
@@ -2752,20 +2742,15 @@ func convertWorkspace(
NextStartAt: nextStartAt,
IsPrebuild: workspace.IsPrebuild(),
TaskID: workspace.TaskID,
SharedWith: sharedWorkspaceActors(ctx, experiments, logger, workspace),
SharedWith: sharedWorkspaceActors(ctx, logger, workspace),
}, nil
}
func sharedWorkspaceActors(
ctx context.Context,
experiments codersdk.Experiments,
logger slog.Logger,
workspace database.Workspace,
) []codersdk.SharedWorkspaceActor {
if !experiments.Enabled(codersdk.ExperimentWorkspaceSharing) {
return nil
}
out := make([]codersdk.SharedWorkspaceActor, 0, len(workspace.UserACL)+len(workspace.GroupACL))
// Users
+7 -25
View File
@@ -1899,7 +1899,6 @@ func TestWorkspaceFilter(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
@@ -1937,7 +1936,6 @@ func TestWorkspaceFilter(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
@@ -1975,7 +1973,6 @@ func TestWorkspaceFilter(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
@@ -2013,7 +2010,6 @@ func TestWorkspaceFilter(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
@@ -5249,7 +5245,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dv,
@@ -5285,7 +5281,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dv,
@@ -5318,7 +5314,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dv,
@@ -5358,7 +5354,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Cleanup(func() { rbac.SetWorkspaceACLDisabled(prevWorkspaceACLDisabled) })
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dv,
@@ -5426,11 +5422,7 @@ func TestDeleteWorkspaceACL(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
admin = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
@@ -5461,11 +5453,7 @@ func TestDeleteWorkspaceACL(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
admin = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
sharedUseClient, toShareWithUser = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
@@ -5504,11 +5492,7 @@ func TestWorkspaceReadCanListACL(t *testing.T) {
t.Cleanup(func() { rbac.SetWorkspaceACLDisabled(prevWorkspaceACLDisabled) })
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
})
client, db = coderdtest.NewWithDatabase(t, nil)
admin = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
sharedUserClientA, sharedUserA = coderdtest.CreateAnotherUser(t, client, admin.OrganizationID)
@@ -5558,7 +5542,6 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
// DisableWorkspaceSharing is false (default)
}),
})
@@ -5592,7 +5575,6 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
dv.DisableWorkspaceSharing = true
}),
})
+1 -5
View File
@@ -3042,7 +3042,7 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
},
{
Name: "Disable Workspace Sharing",
Description: `Disable workspace sharing (requires the "workspace-sharing" experiment to be enabled). Workspace ACL checking is disabled and only owners can have ssh, apps and terminal access to workspaces. Access based on the 'owner' role is also allowed unless disabled via --disable-owner-workspace-access.`,
Description: `Disable workspace sharing. Workspace ACL checking is disabled and only owners can have ssh, apps and terminal access to workspaces. Access based on the 'owner' role is also allowed unless disabled via --disable-owner-workspace-access.`,
Flag: "disable-workspace-sharing",
Env: "CODER_DISABLE_WORKSPACE_SHARING",
@@ -4265,7 +4265,6 @@ const (
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
ExperimentOAuth2 Experiment = "oauth2" // Enables OAuth2 provider functionality.
ExperimentMCPServerHTTP Experiment = "mcp-server-http" // Enables the MCP HTTP server functionality.
ExperimentWorkspaceSharing Experiment = "workspace-sharing" // Enables updating workspace ACLs for sharing with users and groups.
)
func (e Experiment) DisplayName() string {
@@ -4284,8 +4283,6 @@ func (e Experiment) DisplayName() string {
return "OAuth2 Provider Functionality"
case ExperimentMCPServerHTTP:
return "MCP HTTP Server Functionality"
case ExperimentWorkspaceSharing:
return "Workspace Sharing"
default:
// Split on hyphen and convert to title case
// e.g. "web-push" -> "Web Push", "mcp-server-http" -> "Mcp Server Http"
@@ -4303,7 +4300,6 @@ var ExperimentsKnown = Experiments{
ExperimentWebPush,
ExperimentOAuth2,
ExperimentMCPServerHTTP,
ExperimentWorkspaceSharing,
}
// ExperimentsSafe should include all experiments that are safe for
+1 -1
View File
@@ -325,7 +325,7 @@
"description": "Sharing workspaces",
"path": "./user-guides/shared-workspaces.md",
"icon_path": "./images/icons/generic.svg",
"state": ["early access"]
"state": ["beta"]
},
{
"title": "Workspace Scheduling",
+3 -3
View File
@@ -3981,9 +3981,9 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
#### Enumerated Values
| Value(s) |
|-------------------------------------------------------------------------------------------------------------------------------------|
| `auto-fill-parameters`, `example`, `mcp-server-http`, `notifications`, `oauth2`, `web-push`, `workspace-sharing`, `workspace-usage` |
| Value(s) |
|----------------------------------------------------------------------------------------------------------------|
| `auto-fill-parameters`, `example`, `mcp-server-http`, `notifications`, `oauth2`, `web-push`, `workspace-usage` |
## codersdk.ExternalAPIKeyScopes
+1 -1
View File
@@ -1167,7 +1167,7 @@ Remove the permission for the 'owner' role to have workspace execution on all wo
| Environment | <code>$CODER_DISABLE_WORKSPACE_SHARING</code> |
| YAML | <code>disableWorkspaceSharing</code> |
Disable workspace sharing (requires the "workspace-sharing" experiment to be enabled). Workspace ACL checking is disabled and only owners can have ssh, apps and terminal access to workspaces. Access based on the 'owner' role is also allowed unless disabled via --disable-owner-workspace-access.
Disable workspace sharing. Workspace ACL checking is disabled and only owners can have ssh, apps and terminal access to workspaces. Access based on the 'owner' role is also allowed unless disabled via --disable-owner-workspace-access.
### --session-duration
-30
View File
@@ -31,11 +31,6 @@ func TestSharingShare(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
@@ -84,11 +79,6 @@ func TestSharingShare(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
@@ -140,11 +130,6 @@ func TestSharingShare(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
@@ -198,11 +183,6 @@ func TestSharingStatus(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
@@ -255,11 +235,6 @@ func TestSharingRemove(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
@@ -328,11 +303,6 @@ func TestSharingRemove(t *testing.T) {
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
+3 -4
View File
@@ -50,10 +50,9 @@ OPTIONS:
security purposes if a --wildcard-access-url is configured.
--disable-workspace-sharing bool, $CODER_DISABLE_WORKSPACE_SHARING
Disable workspace sharing (requires the "workspace-sharing" experiment
to be enabled). Workspace ACL checking is disabled and only owners can
have ssh, apps and terminal access to workspaces. Access based on the
'owner' role is also allowed unless disabled via
Disable workspace sharing. Workspace ACL checking is disabled and only
owners can have ssh, apps and terminal access to workspaces. Access
based on the 'owner' role is also allowed unless disabled via
--disable-owner-workspace-access.
--swagger-enable bool, $CODER_SWAGGER_ENABLE
-3
View File
@@ -364,9 +364,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
r.Get("/idpsync/field-values", api.organizationIDPSyncClaimFieldValues)
r.Route("/workspace-sharing", func(r chi.Router) {
r.Use(
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentWorkspaceSharing),
)
r.Get("/", api.workspaceSharingSettings)
r.Patch("/", api.patchWorkspaceSharingSettings)
})
+2 -11
View File
@@ -3651,7 +3651,6 @@ func TestWorkspacesFiltering(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
ownerClient, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -3703,7 +3702,6 @@ func TestWorkspacesFiltering(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
ownerClient, db, owner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
@@ -3757,7 +3755,6 @@ func TestWorkspacesFiltering(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
ownerClient, db, owner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
@@ -3806,7 +3803,6 @@ func TestWorkspacesFiltering(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
ownerClient, db, owner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
@@ -3854,7 +3850,6 @@ func TestWorkspacesFiltering(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
var (
ownerClient, db, owner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
@@ -4356,7 +4351,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
IncludeProvisionerDaemon: true,
@@ -4405,7 +4400,7 @@ func TestUpdateWorkspaceACL(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
adminClient := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
DeploymentValues: dv,
@@ -4449,7 +4444,6 @@ func TestDeleteWorkspaceACL(t *testing.T) {
client, db, admin = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
@@ -4493,7 +4487,6 @@ func TestDeleteWorkspaceACL(t *testing.T) {
client, db, admin = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
}),
},
LicenseOptions: &coderdenttest.LicenseOptions{
@@ -4543,7 +4536,6 @@ func TestWorkspacesSharedWith(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, db, user := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -4631,7 +4623,6 @@ func TestWorkspacesSharedWith(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, db, user := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -25,7 +25,6 @@ func TestWorkspaceSharingSettings(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, first := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -45,7 +44,6 @@ func TestWorkspaceSharingSettings(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, first := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -77,7 +75,6 @@ func TestWorkspaceSharingSettings(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, first := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -101,7 +98,6 @@ func TestWorkspaceSharingSettings(t *testing.T) {
auditor := audit.NewMock()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, first := coderdenttest.New(t, &coderdenttest.Options{
AuditLogging: true,
@@ -131,23 +127,6 @@ func TestWorkspaceSharingSettings(t *testing.T) {
require.Equal(t, database.ResourceTypeOrganization, alog.ResourceType)
require.Equal(t, first.OrganizationID, alog.ResourceID)
})
t.Run("ExperimentDisabled", func(t *testing.T) {
t.Parallel()
// Note: NOT setting the experiment flag.
client, first := coderdenttest.New(t, &coderdenttest.Options{})
ctx := testutil.Context(t, testutil.WaitMedium)
memberClient, _ := coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
_, err := memberClient.WorkspaceSharingSettings(ctx, first.OrganizationID.String())
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
require.Contains(t, apiErr.Message, "requires enabling")
require.Contains(t, apiErr.Message, "workspace-sharing")
})
}
func TestWorkspaceSharingDisabled(t *testing.T) {
@@ -157,7 +136,6 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
@@ -211,7 +189,6 @@ func TestWorkspaceSharingDisabled(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
client, db, owner := coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
Options: &coderdtest.Options{
-2
View File
@@ -1939,7 +1939,6 @@ export type Experiment =
| "notifications"
| "oauth2"
| "web-push"
| "workspace-sharing"
| "workspace-usage";
export const Experiments: Experiment[] = [
@@ -1949,7 +1948,6 @@ export const Experiments: Experiment[] = [
"notifications",
"oauth2",
"web-push",
"workspace-sharing",
"workspace-usage",
];
@@ -8,6 +8,7 @@ import { ErrorAlert } from "components/Alert/ErrorAlert";
import { Button } from "components/Button/Button";
import { Checkbox } from "components/Checkbox/Checkbox";
import { DeleteDialog } from "components/Dialogs/DeleteDialog/DeleteDialog";
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
import {
FormFields,
FormFooter,
@@ -147,7 +148,12 @@ export const OrganizationSettingsPageView: FC<
{onToggleWorkspaceSharing && (
<HorizontalContainer className="mt-12">
<HorizontalSection
title="Workspace Sharing"
title={
<div className="flex items-center gap-2">
Workspace Sharing
<FeatureStageBadge contentType="beta" size="sm" />
</div>
}
description="Control whether workspace owners can share their workspaces."
>
<div className="flex items-start gap-3">
@@ -1,4 +1,5 @@
import type { Workspace } from "api/typesGenerated";
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
import { TopbarButton } from "components/FullPageLayout/Topbar";
import {
Popover,
@@ -32,6 +33,10 @@ export const ShareButton: FC<ShareButtonProps> = ({
</TopbarButton>
</PopoverTrigger>
<PopoverContent align="end" className="w-[580px] p-4">
<div className="flex items-center gap-2 mb-4">
<h3 className="text-lg font-semibold m-0">Workspace Sharing</h3>
<FeatureStageBadge contentType="beta" size="sm" />
</div>
<WorkspaceSharingForm
workspaceACL={sharing.workspaceACL}
canUpdatePermissions={canUpdatePermissions}
@@ -1,5 +1,6 @@
import type { Workspace } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { FeatureStageBadge } from "components/FeatureStageBadge/FeatureStageBadge";
import {
Sidebar as BaseSidebar,
SidebarHeader,
@@ -11,7 +12,6 @@ import {
TimerIcon as ScheduleIcon,
Users as SharingIcon,
} from "lucide-react";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react";
interface SidebarProps {
@@ -25,8 +25,6 @@ export const Sidebar: FC<SidebarProps> = ({
workspace,
sharingDisabled,
}) => {
const { experiments } = useDashboard();
return (
<BaseSidebar>
<SidebarHeader
@@ -51,9 +49,10 @@ export const Sidebar: FC<SidebarProps> = ({
<SidebarNavItem href="schedule" icon={ScheduleIcon}>
Schedule
</SidebarNavItem>
{experiments.includes("workspace-sharing") && !sharingDisabled && (
{!sharingDisabled && (
<SidebarNavItem href="sharing" icon={SharingIcon}>
Sharing
<FeatureStageBadge contentType="beta" size="sm" />
</SidebarNavItem>
)}
</BaseSidebar>