Files
coder/cli/start.go
Aaron Lehmann 0e3dc2a80f feat: influence parameter defaults through cli flag/env (#13039)
* feat: influence parameter defaults through cli flag/env

Add a --parameter-default flag / CODER_RICH_PARAMETER_DEFAULT
environment variable which overrides default values suggested for
parameters.

This allows scripts or middleware wrapping the CLI to substitute
defaults for parameter values beyond those defined at the template
level. For example, Git repository/branch parameters can be given
defaults based on the current checkout, or default parameter values can
be parsed out of files inside the repo.

* Rename defaults arg to defaultOverrides
2024-04-29 14:23:54 -04:00

156 lines
5.3 KiB
Go

package cli
import (
"fmt"
"net/http"
"time"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
func (r *RootCmd) start() *serpent.Command {
var parameterFlags workspaceParameterFlags
client := new(codersdk.Client)
cmd := &serpent.Command{
Annotations: workspaceCommand,
Use: "start <workspace>",
Short: "Start a workspace",
Middleware: serpent.Chain(
serpent.RequireNArgs(1),
r.InitClient(client),
),
Options: serpent.OptionSet{cliui.SkipPromptOption()},
Handler: func(inv *serpent.Invocation) error {
workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0])
if err != nil {
return err
}
var build codersdk.WorkspaceBuild
switch workspace.LatestBuild.Status {
case codersdk.WorkspaceStatusRunning:
_, _ = fmt.Fprintf(
inv.Stdout, "\nThe %s workspace is already running!\n",
cliui.Keyword(workspace.Name),
)
return nil
case codersdk.WorkspaceStatusStarting:
_, _ = fmt.Fprintf(
inv.Stdout, "\nThe %s workspace is already starting.\n",
cliui.Keyword(workspace.Name),
)
build = workspace.LatestBuild
default:
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.StatusForbidden {
_, _ = fmt.Fprintln(inv.Stdout, "Unable to start the workspace with the template version from the 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)
}
} else if err != nil {
return err
}
}
err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, build.ID)
if err != nil {
return err
}
_, _ = fmt.Fprintf(
inv.Stdout, "\nThe %s workspace has been started at %s!\n",
cliui.Keyword(workspace.Name), cliui.Timestamp(time.Now()),
)
return nil
},
}
cmd.Options = append(cmd.Options, parameterFlags.allOptions()...)
return cmd
}
func buildWorkspaceStartRequest(inv *serpent.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
}
}
lastBuildParameters, err := client.WorkspaceBuildParameters(inv.Context(), workspace.LatestBuild.ID)
if err != nil {
return codersdk.CreateWorkspaceBuildRequest{}, err
}
buildOptions, err := asWorkspaceBuildParameters(parameterFlags.buildOptions)
if err != nil {
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse build options: %w", err)
}
cliRichParameters, err := asWorkspaceBuildParameters(parameterFlags.richParameters)
if err != nil {
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameters: %w", err)
}
cliRichParameterDefaults, err := asWorkspaceBuildParameters(parameterFlags.richParameterDefaults)
if err != nil {
return codersdk.CreateWorkspaceBuildRequest{}, xerrors.Errorf("unable to parse rich parameter defaults: %w", err)
}
buildParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
Action: action,
TemplateVersionID: version,
NewWorkspaceName: workspace.Name,
LastBuildParameters: lastBuildParameters,
PromptBuildOptions: parameterFlags.promptBuildOptions,
BuildOptions: buildOptions,
PromptRichParameters: parameterFlags.promptRichParameters,
RichParameters: cliRichParameters,
RichParameterFile: parameterFlags.richParameterFile,
RichParameterDefaults: cliRichParameterDefaults,
})
if err != nil {
return codersdk.CreateWorkspaceBuildRequest{}, err
}
return codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionStart,
RichParameterValues: buildParameters,
TemplateVersionID: version,
}, nil
}
func startWorkspace(inv *serpent.Invocation, client *codersdk.Client, workspace codersdk.Workspace, parameterFlags workspaceParameterFlags, action WorkspaceCLIAction) (codersdk.WorkspaceBuild, error) {
if workspace.DormantAt != nil {
_, _ = fmt.Fprintln(inv.Stdout, "Activating dormant workspace...")
err := client.UpdateWorkspaceDormancy(inv.Context(), workspace.ID, codersdk.UpdateWorkspaceDormancy{
Dormant: false,
})
if err != nil {
return codersdk.WorkspaceBuild{}, xerrors.Errorf("activate workspace: %w", err)
}
}
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
}