mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: add ability to make workspace for other user from cli (#8481)
* feat: add ability to make workspace for other user from cli * Add example to show functionality
This commit is contained in:
@ -31,6 +31,12 @@ func (r *RootCmd) create() *clibase.Cmd {
|
||||
Annotations: workspaceCommand,
|
||||
Use: "create [name]",
|
||||
Short: "Create a workspace",
|
||||
Long: formatExamples(
|
||||
example{
|
||||
Description: "Create a workspace for another user (if you have permission)",
|
||||
Command: "coder create <username>/<workspace_name>",
|
||||
},
|
||||
),
|
||||
Middleware: clibase.Chain(r.InitClient(client)),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
organization, err := CurrentOrganization(inv, client)
|
||||
@ -38,8 +44,12 @@ func (r *RootCmd) create() *clibase.Cmd {
|
||||
return err
|
||||
}
|
||||
|
||||
workspaceOwner := codersdk.Me
|
||||
if len(inv.Args) >= 1 {
|
||||
workspaceName = inv.Args[0]
|
||||
workspaceOwner, workspaceName, err = splitNamedWorkspace(inv.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if workspaceName == "" {
|
||||
@ -58,7 +68,7 @@ func (r *RootCmd) create() *clibase.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
_, err = client.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
|
||||
_, err = client.WorkspaceByOwnerAndName(inv.Context(), workspaceOwner, workspaceName, codersdk.WorkspaceOptions{})
|
||||
if err == nil {
|
||||
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
|
||||
}
|
||||
@ -146,7 +156,7 @@ func (r *RootCmd) create() *clibase.Cmd {
|
||||
ttlMillis = &template.MaxTTLMillis
|
||||
}
|
||||
|
||||
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, codersdk.Me, codersdk.CreateWorkspaceRequest{
|
||||
workspace, err := client.CreateWorkspace(inv.Context(), organization.ID, workspaceOwner, codersdk.CreateWorkspaceRequest{
|
||||
TemplateID: template.ID,
|
||||
Name: workspaceName,
|
||||
AutostartSchedule: schedSpec,
|
||||
|
@ -79,6 +79,63 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("CreateForOtherUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
owner := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: provisionCompleteWithAgent,
|
||||
ProvisionPlan: provisionCompleteWithAgent,
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
_, user := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||
args := []string{
|
||||
"create",
|
||||
user.Username + "/their-workspace",
|
||||
"--template", template.Name,
|
||||
"--start-at", "9:30AM Mon-Fri US/Central",
|
||||
"--stop-after", "8h",
|
||||
}
|
||||
|
||||
inv, root := clitest.New(t, args...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
doneChan := make(chan struct{})
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
go func() {
|
||||
defer close(doneChan)
|
||||
err := inv.Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
matches := []struct {
|
||||
match string
|
||||
write string
|
||||
}{
|
||||
{match: "compute.main"},
|
||||
{match: "smith (linux, i386)"},
|
||||
{match: "Confirm create", write: "yes"},
|
||||
}
|
||||
for _, m := range matches {
|
||||
pty.ExpectMatch(m.match)
|
||||
if len(m.write) > 0 {
|
||||
pty.WriteLine(m.write)
|
||||
}
|
||||
}
|
||||
<-doneChan
|
||||
|
||||
ws, err := client.WorkspaceByOwnerAndName(context.Background(), user.Username, "their-workspace", codersdk.WorkspaceOptions{})
|
||||
if assert.NoError(t, err, "expected workspace to be created") {
|
||||
assert.Equal(t, ws.TemplateName, template.Name)
|
||||
if assert.NotNil(t, ws.AutostartSchedule) {
|
||||
assert.Equal(t, *ws.AutostartSchedule, "CRON_TZ=US/Central 30 9 * * Mon-Fri")
|
||||
}
|
||||
if assert.NotNil(t, ws.TTLMillis) {
|
||||
assert.Equal(t, *ws.TTLMillis, 8*time.Hour.Milliseconds())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("InheritStopAfterFromTemplate", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
|
32
cli/root.go
32
cli/root.go
@ -626,24 +626,30 @@ func CurrentOrganization(inv *clibase.Invocation, client *codersdk.Client) (code
|
||||
return orgs[0], nil
|
||||
}
|
||||
|
||||
func splitNamedWorkspace(identifier string) (owner string, workspaceName string, err error) {
|
||||
parts := strings.Split(identifier, "/")
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
owner = codersdk.Me
|
||||
workspaceName = parts[0]
|
||||
case 2:
|
||||
owner = parts[0]
|
||||
workspaceName = parts[1]
|
||||
default:
|
||||
return "", "", xerrors.Errorf("invalid workspace name: %q", identifier)
|
||||
}
|
||||
return owner, workspaceName, nil
|
||||
}
|
||||
|
||||
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
|
||||
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
|
||||
// where user is either a username or UUID.
|
||||
func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
|
||||
parts := strings.Split(identifier, "/")
|
||||
|
||||
var owner, name string
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
owner = codersdk.Me
|
||||
name = parts[0]
|
||||
case 2:
|
||||
owner = parts[0]
|
||||
name = parts[1]
|
||||
default:
|
||||
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
|
||||
owner, name, err := splitNamedWorkspace(identifier)
|
||||
if err != nil {
|
||||
return codersdk.Workspace{}, err
|
||||
}
|
||||
|
||||
return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{})
|
||||
}
|
||||
|
||||
|
4
cli/testdata/coder_create_--help.golden
vendored
4
cli/testdata/coder_create_--help.golden
vendored
@ -2,6 +2,10 @@ Usage: coder create [flags] [name]
|
||||
|
||||
Create a workspace
|
||||
|
||||
- Create a workspace for another user (if you have permission):
|
||||
|
||||
[40m [0m[91;40m$ coder create <username>/<workspace_name>[0m[40m [0m
|
||||
|
||||
[1mOptions[0m
|
||||
--build-options bool
|
||||
Prompt for one-time build options defined with ephemeral parameters.
|
||||
|
@ -10,6 +10,14 @@ Create a workspace
|
||||
coder create [flags] [name]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
```console
|
||||
- Create a workspace for another user (if you have permission):
|
||||
|
||||
$ coder create <username>/<workspace_name>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### --build-options
|
||||
|
Reference in New Issue
Block a user