mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat(cli): add --no-wait flag to coder create (#22867)
Adds a `--no-wait` flag (CODER_CREATE_NO_WAIT) to the create command, matching the existing pattern in `coder start`. When set, the `coder create` command returns immediately after the workspace creation API call succeeds instead of streaming build logs until completion. This enables fire-and-forget workspace creation in CI/automation contexts (e.g., GitHub Actions), where waiting for the build to finish is unnecessary. Combined with other existing flags, users can create a workspace with no interactivity, assuming the user is already authenticated.
This commit is contained in:
@@ -46,6 +46,7 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
|
|||||||
autoUpdates string
|
autoUpdates string
|
||||||
copyParametersFrom string
|
copyParametersFrom string
|
||||||
useParameterDefaults bool
|
useParameterDefaults bool
|
||||||
|
noWait bool
|
||||||
// Organization context is only required if more than 1 template
|
// Organization context is only required if more than 1 template
|
||||||
// shares the same name across multiple organizations.
|
// shares the same name across multiple organizations.
|
||||||
orgContext = NewOrganizationContext()
|
orgContext = NewOrganizationContext()
|
||||||
@@ -372,6 +373,14 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
|
|||||||
|
|
||||||
cliutil.WarnMatchedProvisioners(inv.Stderr, workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)
|
cliutil.WarnMatchedProvisioners(inv.Stderr, workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)
|
||||||
|
|
||||||
|
if noWait {
|
||||||
|
_, _ = fmt.Fprintf(inv.Stdout,
|
||||||
|
"\nThe %s workspace has been created and is building in the background.\n",
|
||||||
|
cliui.Keyword(workspace.Name),
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, workspace.LatestBuild.ID)
|
err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, workspace.LatestBuild.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("watch build: %w", err)
|
return xerrors.Errorf("watch build: %w", err)
|
||||||
@@ -445,6 +454,12 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command {
|
|||||||
Description: "Automatically accept parameter defaults when no value is provided.",
|
Description: "Automatically accept parameter defaults when no value is provided.",
|
||||||
Value: serpent.BoolOf(&useParameterDefaults),
|
Value: serpent.BoolOf(&useParameterDefaults),
|
||||||
},
|
},
|
||||||
|
serpent.Option{
|
||||||
|
Flag: "no-wait",
|
||||||
|
Env: "CODER_CREATE_NO_WAIT",
|
||||||
|
Description: "Return immediately after creating the workspace. The build will run in the background.",
|
||||||
|
Value: serpent.BoolOf(&noWait),
|
||||||
|
},
|
||||||
cliui.SkipPromptOption(),
|
cliui.SkipPromptOption(),
|
||||||
)
|
)
|
||||||
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
|
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
|
||||||
|
|||||||
@@ -603,6 +603,81 @@ func TestCreate(t *testing.T) {
|
|||||||
assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil")
|
assert.Nil(t, ws.AutostartSchedule, "expected workspace autostart schedule to be nil")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("NoWait", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitLong)
|
||||||
|
inv, root := clitest.New(t, "create", "my-workspace",
|
||||||
|
"--template", template.Name,
|
||||||
|
"-y",
|
||||||
|
"--no-wait",
|
||||||
|
)
|
||||||
|
clitest.SetupConfig(t, member, root)
|
||||||
|
doneChan := make(chan struct{})
|
||||||
|
pty := ptytest.New(t).Attach(inv)
|
||||||
|
go func() {
|
||||||
|
defer close(doneChan)
|
||||||
|
err := inv.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
pty.ExpectMatchContext(ctx, "building in the background")
|
||||||
|
_ = testutil.TryReceive(ctx, t, doneChan)
|
||||||
|
|
||||||
|
// Verify workspace was actually created.
|
||||||
|
ws, err := member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ws.TemplateName, template.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoWaitWithParameterDefaults", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
|
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses([]*proto.RichParameter{
|
||||||
|
{Name: "region", Type: "string", DefaultValue: "us-east-1"},
|
||||||
|
{Name: "instance_type", Type: "string", DefaultValue: "t3.micro"},
|
||||||
|
}))
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitLong)
|
||||||
|
inv, root := clitest.New(t, "create", "my-workspace",
|
||||||
|
"--template", template.Name,
|
||||||
|
"-y",
|
||||||
|
"--use-parameter-defaults",
|
||||||
|
"--no-wait",
|
||||||
|
)
|
||||||
|
clitest.SetupConfig(t, member, root)
|
||||||
|
doneChan := make(chan struct{})
|
||||||
|
pty := ptytest.New(t).Attach(inv)
|
||||||
|
go func() {
|
||||||
|
defer close(doneChan)
|
||||||
|
err := inv.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
pty.ExpectMatchContext(ctx, "building in the background")
|
||||||
|
_ = testutil.TryReceive(ctx, t, doneChan)
|
||||||
|
|
||||||
|
// Verify workspace was created and parameters were applied.
|
||||||
|
ws, err := member.WorkspaceByOwnerAndName(ctx, codersdk.Me, "my-workspace", codersdk.WorkspaceOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, ws.TemplateName, template.Name)
|
||||||
|
|
||||||
|
buildParams, err := member.WorkspaceBuildParameters(ctx, ws.LatestBuild.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "region", Value: "us-east-1"})
|
||||||
|
assert.Contains(t, buildParams, codersdk.WorkspaceBuildParameter{Name: "instance_type", Value: "t3.micro"})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
func prepareEchoResponses(parameters []*proto.RichParameter, presets ...*proto.Preset) *echo.Responses {
|
||||||
|
|||||||
+4
@@ -20,6 +20,10 @@ OPTIONS:
|
|||||||
--copy-parameters-from string, $CODER_WORKSPACE_COPY_PARAMETERS_FROM
|
--copy-parameters-from string, $CODER_WORKSPACE_COPY_PARAMETERS_FROM
|
||||||
Specify the source workspace name to copy parameters from.
|
Specify the source workspace name to copy parameters from.
|
||||||
|
|
||||||
|
--no-wait bool, $CODER_CREATE_NO_WAIT
|
||||||
|
Return immediately after creating the workspace. The build will run in
|
||||||
|
the background.
|
||||||
|
|
||||||
--parameter string-array, $CODER_RICH_PARAMETER
|
--parameter string-array, $CODER_RICH_PARAMETER
|
||||||
Rich parameter value in the format "name=value".
|
Rich parameter value in the format "name=value".
|
||||||
|
|
||||||
|
|||||||
Generated
+9
@@ -92,6 +92,15 @@ Specify the source workspace name to copy parameters from.
|
|||||||
|
|
||||||
Automatically accept parameter defaults when no value is provided.
|
Automatically accept parameter defaults when no value is provided.
|
||||||
|
|
||||||
|
### --no-wait
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-------------|------------------------------------|
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
| Environment | <code>$CODER_CREATE_NO_WAIT</code> |
|
||||||
|
|
||||||
|
Return immediately after creating the workspace. The build will run in the background.
|
||||||
|
|
||||||
### -y, --yes
|
### -y, --yes
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|
|||||||
@@ -92,6 +92,15 @@ Specify the source workspace name to copy parameters from.
|
|||||||
|
|
||||||
Automatically accept parameter defaults when no value is provided.
|
Automatically accept parameter defaults when no value is provided.
|
||||||
|
|
||||||
|
### --no-wait
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|-------------|------------------------------------|
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
| Environment | <code>$CODER_CREATE_NO_WAIT</code> |
|
||||||
|
|
||||||
|
Return immediately after creating the workspace. The build will run in the background.
|
||||||
|
|
||||||
### -y, --yes
|
### -y, --yes
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ OPTIONS:
|
|||||||
--copy-parameters-from string, $CODER_WORKSPACE_COPY_PARAMETERS_FROM
|
--copy-parameters-from string, $CODER_WORKSPACE_COPY_PARAMETERS_FROM
|
||||||
Specify the source workspace name to copy parameters from.
|
Specify the source workspace name to copy parameters from.
|
||||||
|
|
||||||
|
--no-wait bool, $CODER_CREATE_NO_WAIT
|
||||||
|
Return immediately after creating the workspace. The build will run in
|
||||||
|
the background.
|
||||||
|
|
||||||
--parameter string-array, $CODER_RICH_PARAMETER
|
--parameter string-array, $CODER_RICH_PARAMETER
|
||||||
Rich parameter value in the format "name=value".
|
Rich parameter value in the format "name=value".
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user