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:
Steven Masley
2023-07-14 09:48:02 -04:00
committed by GitHub
parent 5fd77ad7cf
commit 4c4d966c7b
5 changed files with 102 additions and 17 deletions

View File

@ -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,

View File

@ -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})

View File

@ -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{})
}

View File

@ -2,6 +2,10 @@ Usage: coder create [flags] [name]
Create a workspace
- Create a workspace for another user (if you have permission):
 $ coder create <username>/<workspace_name> 
Options
--build-options bool
Prompt for one-time build options defined with ephemeral parameters.

View File

@ -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