mirror of https://github.com/coder/coder.git
feat: add cli support for workspace automatic updates (#10438)
This commit is contained in:
parent
e756baa0c4
commit
2dce4151ba
|
@ -0,0 +1,58 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func (r *RootCmd) autoupdate() *clibase.Cmd {
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Annotations: workspaceCommand,
|
||||
Use: "autoupdate <workspace> <always|never>",
|
||||
Short: "Toggle auto-update policy for a workspace",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireNArgs(2),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
policy := strings.ToLower(inv.Args[1])
|
||||
err := validateAutoUpdatePolicy(policy)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("validate policy: %w", err)
|
||||
}
|
||||
|
||||
workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get workspace: %w", err)
|
||||
}
|
||||
|
||||
err = client.UpdateWorkspaceAutomaticUpdates(inv.Context(), workspace.ID, codersdk.UpdateWorkspaceAutomaticUpdatesRequest{
|
||||
AutomaticUpdates: codersdk.AutomaticUpdates(policy),
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace automatic updates policy: %w", err)
|
||||
}
|
||||
_, _ = fmt.Fprintf(inv.Stdout, "Updated workspace %q auto-update policy to %q\n", workspace.Name, policy)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Options = append(cmd.Options, cliui.SkipPromptOption())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateAutoUpdatePolicy(arg string) error {
|
||||
switch codersdk.AutomaticUpdates(arg) {
|
||||
case codersdk.AutomaticUpdatesAlways, codersdk.AutomaticUpdatesNever:
|
||||
return nil
|
||||
default:
|
||||
return xerrors.Errorf("invalid option %q must be either of %q or %q", arg, codersdk.AutomaticUpdatesAlways, codersdk.AutomaticUpdatesNever)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
func TestAutoUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", 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)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
require.Equal(t, codersdk.AutomaticUpdatesNever, workspace.AutomaticUpdates)
|
||||
|
||||
expectedPolicy := codersdk.AutomaticUpdatesAlways
|
||||
inv, root := clitest.New(t, "autoupdate", workspace.Name, string(expectedPolicy))
|
||||
clitest.SetupConfig(t, member, root)
|
||||
var buf bytes.Buffer
|
||||
inv.Stdout = &buf
|
||||
err := inv.Run()
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, buf.String(), fmt.Sprintf("Updated workspace %q auto-update policy to %q", workspace.Name, expectedPolicy))
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
require.Equal(t, expectedPolicy, workspace.AutomaticUpdates)
|
||||
})
|
||||
|
||||
t.Run("InvalidArgs", func(t *testing.T) {
|
||||
type testcase struct {
|
||||
Name string
|
||||
Args []string
|
||||
ErrorContains string
|
||||
}
|
||||
|
||||
cases := []testcase{
|
||||
{
|
||||
Name: "NoPolicy",
|
||||
Args: []string{"autoupdate", "ws"},
|
||||
ErrorContains: "wanted 2 args but got 1",
|
||||
},
|
||||
{
|
||||
Name: "InvalidPolicy",
|
||||
Args: []string{"autoupdate", "ws", "sometimes"},
|
||||
ErrorContains: `invalid option "sometimes" must be either of`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
inv, root := clitest.New(t, c.Args...)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
err := inv.Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.ErrorContains)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
|
@ -140,9 +140,9 @@ func (r *RootCmd) create() *clibase.Cmd {
|
|||
}
|
||||
|
||||
richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
|
||||
Action: WorkspaceCreate,
|
||||
Template: template,
|
||||
NewWorkspaceName: workspaceName,
|
||||
Action: WorkspaceCreate,
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
NewWorkspaceName: workspaceName,
|
||||
|
||||
RichParameterFile: parameterFlags.richParameterFile,
|
||||
RichParameters: cliRichParameters,
|
||||
|
@ -224,10 +224,9 @@ func (r *RootCmd) create() *clibase.Cmd {
|
|||
}
|
||||
|
||||
type prepWorkspaceBuildArgs struct {
|
||||
Action WorkspaceCLIAction
|
||||
Template codersdk.Template
|
||||
NewWorkspaceName string
|
||||
WorkspaceID uuid.UUID
|
||||
Action WorkspaceCLIAction
|
||||
TemplateVersionID uuid.UUID
|
||||
NewWorkspaceName string
|
||||
|
||||
LastBuildParameters []codersdk.WorkspaceBuildParameter
|
||||
|
||||
|
@ -244,7 +243,7 @@ type prepWorkspaceBuildArgs struct {
|
|||
func prepWorkspaceBuild(inv *clibase.Invocation, client *codersdk.Client, args prepWorkspaceBuildArgs) ([]codersdk.WorkspaceBuildParameter, error) {
|
||||
ctx := inv.Context()
|
||||
|
||||
templateVersion, err := client.TemplateVersion(ctx, args.Template.ActiveVersionID)
|
||||
templateVersion, err := client.TemplateVersion(ctx, args.TemplateVersionID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version: %w", err)
|
||||
}
|
||||
|
|
|
@ -600,9 +600,9 @@ func (r *RootCmd) scaletestCreateWorkspaces() *clibase.Cmd {
|
|||
}
|
||||
|
||||
richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
|
||||
Action: WorkspaceCreate,
|
||||
Template: tpl,
|
||||
NewWorkspaceName: "scaletest-N", // TODO: the scaletest runner will pass in a different name here. Does this matter?
|
||||
Action: WorkspaceCreate,
|
||||
TemplateVersionID: tpl.ActiveVersionID,
|
||||
NewWorkspaceName: "scaletest-N", // TODO: the scaletest runner will pass in a different name here. Does this matter?
|
||||
|
||||
RichParameterFile: parameterFlags.richParameterFile,
|
||||
RichParameters: cliRichParameters,
|
||||
|
|
|
@ -20,6 +20,13 @@ type workspaceParameterFlags struct {
|
|||
|
||||
richParameterFile string
|
||||
richParameters []string
|
||||
|
||||
promptRichParameters bool
|
||||
}
|
||||
|
||||
func (wpf *workspaceParameterFlags) allOptions() []clibase.Option {
|
||||
options := append(wpf.cliBuildOptions(), wpf.cliParameters()...)
|
||||
return append(options, wpf.alwaysPrompt())
|
||||
}
|
||||
|
||||
func (wpf *workspaceParameterFlags) cliBuildOptions() []clibase.Option {
|
||||
|
@ -55,6 +62,14 @@ func (wpf *workspaceParameterFlags) cliParameters() []clibase.Option {
|
|||
}
|
||||
}
|
||||
|
||||
func (wpf *workspaceParameterFlags) alwaysPrompt() clibase.Option {
|
||||
return clibase.Option{
|
||||
Flag: "always-prompt",
|
||||
Description: "Always prompt all parameters. Does not pull parameter values from existing workspace.",
|
||||
Value: clibase.BoolOf(&wpf.promptRichParameters),
|
||||
}
|
||||
}
|
||||
|
||||
func asWorkspaceBuildParameters(nameValuePairs []string) ([]codersdk.WorkspaceBuildParameter, error) {
|
||||
var params []codersdk.WorkspaceBuildParameter
|
||||
for _, nameValue := range nameValuePairs {
|
||||
|
|
|
@ -194,7 +194,7 @@ func (pr *ParameterResolver) resolveWithInput(resolved []codersdk.WorkspaceBuild
|
|||
(action == WorkspaceUpdate && promptParameterOption) ||
|
||||
(action == WorkspaceUpdate && tvp.Mutable && tvp.Required) ||
|
||||
(action == WorkspaceUpdate && !tvp.Mutable && firstTimeUse) ||
|
||||
(action == WorkspaceUpdate && tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) {
|
||||
(tvp.Mutable && !tvp.Ephemeral && pr.promptRichParameters) {
|
||||
parameterValue, err := cliui.RichParameter(inv, tvp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -25,7 +25,7 @@ func (r *RootCmd) restart() *clibase.Cmd {
|
|||
clibase.RequireNArgs(1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Options: append(parameterFlags.cliBuildOptions(), cliui.SkipPromptOption()),
|
||||
Options: clibase.OptionSet{cliui.SkipPromptOption()},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
ctx := inv.Context()
|
||||
out := inv.Stdout
|
||||
|
@ -35,25 +35,7 @@ func (r *RootCmd) restart() *clibase.Cmd {
|
|||
return err
|
||||
}
|
||||
|
||||
lastBuildParameters, err := client.WorkspaceBuildParameters(inv.Context(), workspace.LatestBuild.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildOptions, err := asWorkspaceBuildParameters(parameterFlags.buildOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("can't parse build options: %w", err)
|
||||
}
|
||||
|
||||
buildParameters, err := prepStartWorkspace(inv, client, prepStartWorkspaceArgs{
|
||||
Action: WorkspaceRestart,
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
BuildOptions: buildOptions,
|
||||
})
|
||||
startReq, err := buildWorkspaceStartRequest(inv, client, workspace, parameterFlags, WorkspaceRestart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -72,27 +54,18 @@ func (r *RootCmd) restart() *clibase.Cmd {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cliui.WorkspaceBuild(ctx, out, client, build.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: buildParameters,
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
}
|
||||
|
||||
build, err = client.CreateWorkspaceBuild(ctx, workspace.ID, req)
|
||||
build, err = client.CreateWorkspaceBuild(ctx, workspace.ID, startReq)
|
||||
// It's possible for a workspace build to fail due to the template requiring starting
|
||||
// workspaces with the active version.
|
||||
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusUnauthorized {
|
||||
build, err = startWorkspaceActiveVersion(inv, client, startWorkspaceActiveVersionArgs{
|
||||
BuildOptions: buildOptions,
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
Workspace: workspace,
|
||||
})
|
||||
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
|
||||
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("start workspace with active template version: %w", err)
|
||||
}
|
||||
|
@ -112,5 +85,8 @@ func (r *RootCmd) restart() *clibase.Cmd {
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Options = append(cmd.Options, parameterFlags.allOptions()...)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -239,4 +239,55 @@ func TestRestartWithParameters(t *testing.T) {
|
|||
Value: immutableParameterValue,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AlwaysPrompt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the workspace
|
||||
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, mutableParamsResponse)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: mutableParameterName,
|
||||
Value: mutableParameterValue,
|
||||
},
|
||||
}
|
||||
})
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
inv, root := clitest.New(t, "restart", workspace.Name, "-y", "--always-prompt")
|
||||
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)
|
||||
}()
|
||||
|
||||
// We should be prompted for the parameters again.
|
||||
newValue := "xyz"
|
||||
pty.ExpectMatch(mutableParameterName)
|
||||
pty.WriteLine(newValue)
|
||||
pty.ExpectMatch("workspace has been restarted")
|
||||
<-doneChan
|
||||
|
||||
// Verify that the updated values are persisted.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{})
|
||||
require.NoError(t, err)
|
||||
actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, actualParameters, codersdk.WorkspaceBuildParameter{
|
||||
Name: mutableParameterName,
|
||||
Value: newValue,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
|
|||
r.version(defaultVersionInfo),
|
||||
|
||||
// Workspace Commands
|
||||
r.autoupdate(),
|
||||
r.configSSH(),
|
||||
r.create(),
|
||||
r.deleteWorkspace(),
|
||||
|
|
130
cli/start.go
130
cli/start.go
|
@ -5,7 +5,6 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
|
@ -25,52 +24,19 @@ func (r *RootCmd) start() *clibase.Cmd {
|
|||
clibase.RequireNArgs(1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Options: append(parameterFlags.cliBuildOptions(), cliui.SkipPromptOption()),
|
||||
Options: clibase.OptionSet{cliui.SkipPromptOption()},
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastBuildParameters, err := client.WorkspaceBuildParameters(inv.Context(), workspace.LatestBuild.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildOptions, err := asWorkspaceBuildParameters(parameterFlags.buildOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to parse build options: %w", err)
|
||||
}
|
||||
|
||||
buildParameters, err := prepStartWorkspace(inv, client, prepStartWorkspaceArgs{
|
||||
Action: WorkspaceStart,
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
BuildOptions: buildOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: buildParameters,
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
}
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(inv.Context(), workspace.ID, req)
|
||||
build, err := startWorkspace(inv, client, workspace, parameterFlags, WorkspaceStart)
|
||||
// It's possible for a workspace build to fail due to the template requiring starting
|
||||
// workspaces with the active version.
|
||||
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusUnauthorized {
|
||||
build, err = startWorkspaceActiveVersion(inv, client, startWorkspaceActiveVersionArgs{
|
||||
BuildOptions: buildOptions,
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
Workspace: workspace,
|
||||
})
|
||||
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
|
||||
build, err = startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("start workspace with active template version: %w", err)
|
||||
}
|
||||
|
@ -90,75 +56,69 @@ func (r *RootCmd) start() *clibase.Cmd {
|
|||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Options = append(cmd.Options, parameterFlags.allOptions()...)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type prepStartWorkspaceArgs struct {
|
||||
Action WorkspaceCLIAction
|
||||
TemplateVersionID uuid.UUID
|
||||
|
||||
LastBuildParameters []codersdk.WorkspaceBuildParameter
|
||||
|
||||
PromptBuildOptions bool
|
||||
BuildOptions []codersdk.WorkspaceBuildParameter
|
||||
}
|
||||
|
||||
func prepStartWorkspace(inv *clibase.Invocation, client *codersdk.Client, args prepStartWorkspaceArgs) ([]codersdk.WorkspaceBuildParameter, error) {
|
||||
ctx := inv.Context()
|
||||
|
||||
templateVersion, err := client.TemplateVersion(ctx, args.TemplateVersionID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version: %w", err)
|
||||
func buildWorkspaceStartRequest(inv *clibase.Invocation, client *codersdk.Client, workspace codersdk.Workspace, parameterFlags workspaceParameterFlags, action WorkspaceCLIAction) (codersdk.CreateWorkspaceBuildRequest, error) {
|
||||
version := workspace.LatestBuild.TemplateVersionID
|
||||
if workspace.AutomaticUpdates == codersdk.AutomaticUpdatesAlways || action == WorkspaceUpdate {
|
||||
version = workspace.TemplateActiveVersionID
|
||||
if version != workspace.LatestBuild.TemplateVersionID {
|
||||
action = WorkspaceUpdate
|
||||
}
|
||||
}
|
||||
|
||||
templateVersionParameters, err := client.TemplateVersionRichParameters(inv.Context(), templateVersion.ID)
|
||||
lastBuildParameters, err := client.WorkspaceBuildParameters(inv.Context(), workspace.LatestBuild.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get template version rich parameters: %w", err)
|
||||
return codersdk.CreateWorkspaceBuildRequest{}, err
|
||||
}
|
||||
|
||||
resolver := new(ParameterResolver).
|
||||
WithLastBuildParameters(args.LastBuildParameters).
|
||||
WithPromptBuildOptions(args.PromptBuildOptions).
|
||||
WithBuildOptions(args.BuildOptions)
|
||||
return resolver.Resolve(inv, args.Action, templateVersionParameters)
|
||||
}
|
||||
|
||||
type startWorkspaceActiveVersionArgs struct {
|
||||
BuildOptions []codersdk.WorkspaceBuildParameter
|
||||
LastBuildParameters []codersdk.WorkspaceBuildParameter
|
||||
PromptBuildOptions bool
|
||||
Workspace codersdk.Workspace
|
||||
}
|
||||
|
||||
func startWorkspaceActiveVersion(inv *clibase.Invocation, client *codersdk.Client, args startWorkspaceActiveVersionArgs) (codersdk.WorkspaceBuild, error) {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
|
||||
|
||||
template, err := client.Template(inv.Context(), args.Workspace.TemplateID)
|
||||
buildOptions, err := asWorkspaceBuildParameters(parameterFlags.buildOptions)
|
||||
if err != nil {
|
||||
return codersdk.WorkspaceBuild{}, xerrors.Errorf("get template: %w", err)
|
||||
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse build options: %w", err)
|
||||
}
|
||||
|
||||
buildParameters, err := prepStartWorkspace(inv, client, prepStartWorkspaceArgs{
|
||||
Action: WorkspaceStart,
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters)
|
||||
if err != nil {
|
||||
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse build options: %w", err)
|
||||
}
|
||||
|
||||
LastBuildParameters: args.LastBuildParameters,
|
||||
buildParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
|
||||
Action: action,
|
||||
TemplateVersionID: version,
|
||||
NewWorkspaceName: workspace.Name,
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
|
||||
PromptBuildOptions: args.PromptBuildOptions,
|
||||
BuildOptions: args.BuildOptions,
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
BuildOptions: buildOptions,
|
||||
PromptRichParameters: parameterFlags.promptRichParameters,
|
||||
RichParameters: cliRichParameters,
|
||||
RichParameterFile: parameterFlags.richParameterFile,
|
||||
})
|
||||
if err != nil {
|
||||
return codersdk.WorkspaceBuild{}, err
|
||||
return codersdk.CreateWorkspaceBuildRequest{}, err
|
||||
}
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(inv.Context(), args.Workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
return codersdk.CreateWorkspaceBuildRequest{
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: buildParameters,
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
})
|
||||
TemplateVersionID: version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func startWorkspace(inv *clibase.Invocation, client *codersdk.Client, workspace codersdk.Workspace, parameterFlags workspaceParameterFlags, action WorkspaceCLIAction) (codersdk.WorkspaceBuild, error) {
|
||||
req, err := buildWorkspaceStartRequest(inv, client, workspace, parameterFlags, action)
|
||||
if err != nil {
|
||||
return codersdk.WorkspaceBuild{}, err
|
||||
}
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(inv.Context(), workspace.ID, req)
|
||||
if err != nil {
|
||||
return codersdk.WorkspaceBuild{}, xerrors.Errorf("create workspace build: %w", err)
|
||||
}
|
||||
|
||||
return build, nil
|
||||
}
|
||||
|
|
|
@ -26,6 +26,52 @@ const (
|
|||
immutableParameterName = "immutable_parameter"
|
||||
immutableParameterDescription = "This is immutable parameter"
|
||||
immutableParameterValue = "abc"
|
||||
|
||||
mutableParameterName = "mutable_parameter"
|
||||
mutableParameterValue = "hello"
|
||||
)
|
||||
|
||||
var (
|
||||
mutableParamsResponse = &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: mutableParameterName,
|
||||
Description: "This is a mutable parameter",
|
||||
Required: true,
|
||||
Mutable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
}
|
||||
|
||||
immutableParamsResponse = &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: immutableParameterName,
|
||||
Description: immutableParameterDescription,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
}
|
||||
)
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
|
@ -147,26 +193,6 @@ func TestStart(t *testing.T) {
|
|||
func TestStartWithParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
echoResponses := &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: immutableParameterName,
|
||||
Description: immutableParameterDescription,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
}
|
||||
|
||||
t.Run("DoNotAskForImmutables", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -174,7 +200,7 @@ func TestStartWithParameters(t *testing.T) {
|
|||
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, echoResponses)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, immutableParamsResponse)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
|
@ -218,4 +244,133 @@ func TestStartWithParameters(t *testing.T) {
|
|||
Value: immutableParameterValue,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("AlwaysPrompt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the workspace
|
||||
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, mutableParamsResponse)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
|
||||
{
|
||||
Name: mutableParameterName,
|
||||
Value: mutableParameterValue,
|
||||
},
|
||||
}
|
||||
})
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
// Stop the workspace
|
||||
workspaceBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspaceBuild.ID)
|
||||
|
||||
// Start the workspace again
|
||||
inv, root := clitest.New(t, "start", workspace.Name, "--always-prompt")
|
||||
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)
|
||||
}()
|
||||
|
||||
newValue := "xyz"
|
||||
pty.ExpectMatch(mutableParameterName)
|
||||
pty.WriteLine(newValue)
|
||||
pty.ExpectMatch("workspace has been started")
|
||||
<-doneChan
|
||||
|
||||
// Verify that the updated values are persisted.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
workspace, err := client.WorkspaceByOwnerAndName(ctx, workspace.OwnerName, workspace.Name, codersdk.WorkspaceOptions{})
|
||||
require.NoError(t, err)
|
||||
actualParameters, err := client.WorkspaceBuildParameters(ctx, workspace.LatestBuild.ID)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, actualParameters, codersdk.WorkspaceBuildParameter{
|
||||
Name: mutableParameterName,
|
||||
Value: newValue,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestStartAutoUpdate also tests restart since the flows are virtually identical.
|
||||
func TestStartAutoUpdate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
stringParameterName = "myparam"
|
||||
stringParameterValue = "abc"
|
||||
)
|
||||
|
||||
stringRichParameters := []*proto.RichParameter{
|
||||
{Name: stringParameterName, Type: "string", Mutable: true, Required: true},
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
Name string
|
||||
Cmd string
|
||||
}
|
||||
|
||||
cases := []testcase{
|
||||
{
|
||||
Name: "StartOK",
|
||||
Cmd: "start",
|
||||
},
|
||||
{
|
||||
Name: "RestartOK",
|
||||
Cmd: "restart",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.Name, 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)
|
||||
version1 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
|
||||
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version1.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, member, owner.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutomaticUpdates = codersdk.AutomaticUpdatesAlways
|
||||
})
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
if c.Cmd == "start" {
|
||||
coderdtest.MustTransitionWorkspace(t, member, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
}
|
||||
version2 := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, prepareEchoResponses(stringRichParameters), func(ctvr *codersdk.CreateTemplateVersionRequest) {
|
||||
ctvr.TemplateID = template.ID
|
||||
})
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
|
||||
coderdtest.UpdateActiveTemplateVersion(t, client, template.ID, version2.ID)
|
||||
|
||||
inv, root := clitest.New(t, c.Cmd, "-y", workspace.Name)
|
||||
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.ExpectMatch(stringParameterName)
|
||||
pty.WriteLine(stringParameterValue)
|
||||
<-doneChan
|
||||
|
||||
workspace = coderdtest.MustWorkspace(t, member, workspace.ID)
|
||||
require.Equal(t, version2.ID, workspace.LatestBuild.TemplateVersionID)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ USAGE:
|
|||
$ coder templates init
|
||||
|
||||
SUBCOMMANDS:
|
||||
autoupdate Toggle auto-update policy for a workspace
|
||||
config-ssh Add an SSH Host entry for your workspaces "ssh
|
||||
coder.workspace"
|
||||
create Create a workspace
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
coder v0.0.0-devel
|
||||
|
||||
USAGE:
|
||||
coder autoupdate [flags] <workspace> <always|never>
|
||||
|
||||
Toggle auto-update policy for a workspace
|
||||
|
||||
OPTIONS:
|
||||
-y, --yes bool
|
||||
Bypass prompts.
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
|
@ -6,12 +6,23 @@ USAGE:
|
|||
Restart a workspace
|
||||
|
||||
OPTIONS:
|
||||
--always-prompt bool
|
||||
Always prompt all parameters. Does not pull parameter values from
|
||||
existing workspace.
|
||||
|
||||
--build-option string-array, $CODER_BUILD_OPTION
|
||||
Build option value in the format "name=value".
|
||||
|
||||
--build-options bool
|
||||
Prompt for one-time build options defined with ephemeral parameters.
|
||||
|
||||
--parameter string-array, $CODER_RICH_PARAMETER
|
||||
Rich parameter value in the format "name=value".
|
||||
|
||||
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
|
||||
Specify a file path with values for rich parameters defined in the
|
||||
template.
|
||||
|
||||
-y, --yes bool
|
||||
Bypass prompts.
|
||||
|
||||
|
|
|
@ -6,12 +6,23 @@ USAGE:
|
|||
Start a workspace
|
||||
|
||||
OPTIONS:
|
||||
--always-prompt bool
|
||||
Always prompt all parameters. Does not pull parameter values from
|
||||
existing workspace.
|
||||
|
||||
--build-option string-array, $CODER_BUILD_OPTION
|
||||
Build option value in the format "name=value".
|
||||
|
||||
--build-options bool
|
||||
Prompt for one-time build options defined with ephemeral parameters.
|
||||
|
||||
--parameter string-array, $CODER_RICH_PARAMETER
|
||||
Rich parameter value in the format "name=value".
|
||||
|
||||
--rich-parameter-file string, $CODER_RICH_PARAMETER_FILE
|
||||
Specify a file path with values for rich parameters defined in the
|
||||
template.
|
||||
|
||||
-y, --yes bool
|
||||
Bypass prompts.
|
||||
|
||||
|
|
|
@ -10,11 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func (r *RootCmd) update() *clibase.Cmd {
|
||||
var (
|
||||
alwaysPrompt bool
|
||||
|
||||
parameterFlags workspaceParameterFlags
|
||||
)
|
||||
var parameterFlags workspaceParameterFlags
|
||||
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
|
@ -31,58 +27,16 @@ func (r *RootCmd) update() *clibase.Cmd {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !workspace.Outdated && !alwaysPrompt && !parameterFlags.promptBuildOptions && len(parameterFlags.buildOptions) == 0 {
|
||||
if !workspace.Outdated && !parameterFlags.promptRichParameters && !parameterFlags.promptBuildOptions && len(parameterFlags.buildOptions) == 0 {
|
||||
_, _ = fmt.Fprintf(inv.Stdout, "Workspace isn't outdated!\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
buildOptions, err := asWorkspaceBuildParameters(parameterFlags.buildOptions)
|
||||
build, err := startWorkspace(inv, client, workspace, parameterFlags, WorkspaceUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
return xerrors.Errorf("start workspace: %w", err)
|
||||
}
|
||||
|
||||
template, err := client.Template(inv.Context(), workspace.TemplateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastBuildParameters, err := client.WorkspaceBuildParameters(inv.Context(), workspace.LatestBuild.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("can't parse given parameter values: %w", err)
|
||||
}
|
||||
|
||||
buildParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
|
||||
Action: WorkspaceUpdate,
|
||||
Template: template,
|
||||
NewWorkspaceName: workspace.Name,
|
||||
WorkspaceID: workspace.LatestBuild.ID,
|
||||
|
||||
LastBuildParameters: lastBuildParameters,
|
||||
|
||||
PromptBuildOptions: parameterFlags.promptBuildOptions,
|
||||
BuildOptions: buildOptions,
|
||||
|
||||
PromptRichParameters: alwaysPrompt,
|
||||
RichParameters: cliRichParameters,
|
||||
RichParameterFile: parameterFlags.richParameterFile,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
build, err := client.CreateWorkspaceBuild(inv.Context(), workspace.ID, codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
Transition: codersdk.WorkspaceTransitionStart,
|
||||
RichParameterValues: buildParameters,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logs, closer, err := client.WorkspaceBuildLogsAfter(inv.Context(), build.ID, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -99,14 +53,6 @@ func (r *RootCmd) update() *clibase.Cmd {
|
|||
},
|
||||
}
|
||||
|
||||
cmd.Options = clibase.OptionSet{
|
||||
{
|
||||
Flag: "always-prompt",
|
||||
Description: "Always prompt all parameters. Does not pull parameter values from existing workspace.",
|
||||
Value: clibase.BoolOf(&alwaysPrompt),
|
||||
},
|
||||
}
|
||||
cmd.Options = append(cmd.Options, parameterFlags.cliBuildOptions()...)
|
||||
cmd.Options = append(cmd.Options, parameterFlags.cliParameters()...)
|
||||
cmd.Options = append(cmd.Options, parameterFlags.allOptions()...)
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -934,11 +934,8 @@ func MustTransitionWorkspace(t testing.TB, client *codersdk.Client, workspaceID
|
|||
require.NoError(t, err, "unexpected error fetching workspace")
|
||||
require.Equal(t, workspace.LatestBuild.Transition, codersdk.WorkspaceTransition(from), "expected workspace state: %s got: %s", from, workspace.LatestBuild.Transition)
|
||||
|
||||
template, err := client.Template(ctx, workspace.TemplateID)
|
||||
require.NoError(t, err, "fetch workspace template")
|
||||
|
||||
req := codersdk.CreateWorkspaceBuildRequest{
|
||||
TemplateVersionID: template.ActiveVersionID,
|
||||
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
|
||||
Transition: codersdk.WorkspaceTransition(to),
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
|
|||
|
||||
| Name | Purpose |
|
||||
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| [<code>autoupdate</code>](./cli/autoupdate.md) | Toggle auto-update policy for a workspace |
|
||||
| [<code>config-ssh</code>](./cli/config-ssh.md) | Add an SSH Host entry for your workspaces "ssh coder.workspace" |
|
||||
| [<code>create</code>](./cli/create.md) | Create a workspace |
|
||||
| [<code>delete</code>](./cli/delete.md) | Delete a workspace |
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# autoupdate
|
||||
|
||||
Toggle auto-update policy for a workspace
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder autoupdate [flags] <workspace> <always|never>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Bypass prompts.
|
|
@ -12,6 +12,14 @@ coder restart [flags] <workspace>
|
|||
|
||||
## Options
|
||||
|
||||
### --always-prompt
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Always prompt all parameters. Does not pull parameter values from existing workspace.
|
||||
|
||||
### --build-option
|
||||
|
||||
| | |
|
||||
|
@ -29,6 +37,24 @@ Build option value in the format "name=value".
|
|||
|
||||
Prompt for one-time build options defined with ephemeral parameters.
|
||||
|
||||
### --parameter
|
||||
|
||||
| | |
|
||||
| ----------- | ---------------------------------- |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER</code> |
|
||||
|
||||
Rich parameter value in the format "name=value".
|
||||
|
||||
### --rich-parameter-file
|
||||
|
||||
| | |
|
||||
| ----------- | --------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_FILE</code> |
|
||||
|
||||
Specify a file path with values for rich parameters defined in the template.
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
|
|
|
@ -12,6 +12,14 @@ coder start [flags] <workspace>
|
|||
|
||||
## Options
|
||||
|
||||
### --always-prompt
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Always prompt all parameters. Does not pull parameter values from existing workspace.
|
||||
|
||||
### --build-option
|
||||
|
||||
| | |
|
||||
|
@ -29,6 +37,24 @@ Build option value in the format "name=value".
|
|||
|
||||
Prompt for one-time build options defined with ephemeral parameters.
|
||||
|
||||
### --parameter
|
||||
|
||||
| | |
|
||||
| ----------- | ---------------------------------- |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER</code> |
|
||||
|
||||
Rich parameter value in the format "name=value".
|
||||
|
||||
### --rich-parameter-file
|
||||
|
||||
| | |
|
||||
| ----------- | --------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_RICH_PARAMETER_FILE</code> |
|
||||
|
||||
Specify a file path with values for rich parameters defined in the template.
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
|
|
|
@ -569,6 +569,11 @@
|
|||
"path": "./cli.md",
|
||||
"icon_path": "./images/icons/terminal.svg",
|
||||
"children": [
|
||||
{
|
||||
"title": "autoupdate",
|
||||
"description": "Toggle auto-update policy for a workspace",
|
||||
"path": "cli/autoupdate.md"
|
||||
},
|
||||
{
|
||||
"title": "coder",
|
||||
"path": "cli.md"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -154,21 +155,26 @@ func TestStart(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, ws.LatestBuild.ID)
|
||||
|
||||
initialTemplateVersion := ws.LatestBuild.TemplateVersionID
|
||||
|
||||
if cmd == "start" {
|
||||
// Stop the workspace so that we can start it.
|
||||
coderdtest.MustTransitionWorkspace(t, c.Client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop, func(req *codersdk.CreateWorkspaceBuildRequest) {
|
||||
req.TemplateVersionID = oldVersion.ID
|
||||
})
|
||||
coderdtest.MustTransitionWorkspace(t, c.Client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
}
|
||||
// Start the workspace. Every test permutation should
|
||||
// pass.
|
||||
var buf bytes.Buffer
|
||||
inv, conf := newCLI(t, cmd, ws.Name, "-y")
|
||||
inv.Stdout = &buf
|
||||
clitest.SetupConfig(t, c.Client, conf)
|
||||
err = inv.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
ws = coderdtest.MustWorkspace(t, c.Client, ws.ID)
|
||||
require.Equal(t, c.ExpectedVersion, ws.LatestBuild.TemplateVersionID)
|
||||
if initialTemplateVersion != ws.LatestBuild.TemplateVersionID {
|
||||
require.Contains(t, buf.String(), "Failed to restart with the template version from your last build. Policy may require you to restart with the current active template version.")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue