2022-02-12 19:34:04 +00:00
package cli
import (
"fmt"
"time"
"github.com/spf13/cobra"
2022-04-14 17:23:20 +00:00
"golang.org/x/exp/slices"
2022-02-12 19:34:04 +00:00
"golang.org/x/xerrors"
2022-04-11 23:54:30 +00:00
"github.com/coder/coder/cli/cliflag"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/cli/cliui"
2022-06-02 10:23:34 +00:00
"github.com/coder/coder/coderd/util/ptr"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/codersdk"
2022-02-12 19:34:04 +00:00
)
2022-05-02 16:08:52 +00:00
func create ( ) * cobra . Command {
2022-03-22 19:17:50 +00:00
var (
2022-06-17 20:38:10 +00:00
parameterFile string
templateName string
startAt string
stopAfter time . Duration
workspaceName string
2022-03-22 19:17:50 +00:00
)
2022-02-12 19:34:04 +00:00
cmd := & cobra . Command {
2022-05-09 22:42:02 +00:00
Annotations : workspaceCommand ,
Use : "create [name]" ,
Short : "Create a workspace from a template" ,
2022-02-12 19:34:04 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
client , err := createClient ( cmd )
if err != nil {
return err
}
2022-04-14 17:23:20 +00:00
2022-02-12 19:34:04 +00:00
organization , err := currentOrganization ( cmd , client )
if err != nil {
return err
}
2022-04-11 23:54:30 +00:00
if len ( args ) >= 1 {
2022-04-14 17:23:20 +00:00
workspaceName = args [ 0 ]
}
if workspaceName == "" {
workspaceName , err = cliui . Prompt ( cmd , cliui . PromptOptions {
Text : "Specify a name for your workspace:" ,
Validate : func ( workspaceName string ) error {
2022-06-10 14:58:42 +00:00
_ , err = client . WorkspaceByOwnerAndName ( cmd . Context ( ) , codersdk . Me , workspaceName , codersdk . WorkspaceOptions { } )
2022-04-14 17:23:20 +00:00
if err == nil {
return xerrors . Errorf ( "A workspace already exists named %q!" , workspaceName )
}
return nil
} ,
} )
if err != nil {
return err
}
}
2022-06-10 14:58:42 +00:00
_ , err = client . WorkspaceByOwnerAndName ( cmd . Context ( ) , codersdk . Me , workspaceName , codersdk . WorkspaceOptions { } )
2022-04-14 17:23:20 +00:00
if err == nil {
return xerrors . Errorf ( "A workspace already exists named %q!" , workspaceName )
2022-04-11 23:54:30 +00:00
}
2022-04-06 17:42:40 +00:00
var template codersdk . Template
if templateName == "" {
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , cliui . Styles . Wrap . Render ( "Select a template below to preview the provisioned infrastructure:" ) )
2022-03-22 19:17:50 +00:00
2022-04-06 17:42:40 +00:00
templates , err := client . TemplatesByOrganization ( cmd . Context ( ) , organization . ID )
2022-03-22 19:17:50 +00:00
if err != nil {
return err
}
2022-04-14 17:23:20 +00:00
slices . SortFunc ( templates , func ( a , b codersdk . Template ) bool {
return a . WorkspaceOwnerCount > b . WorkspaceOwnerCount
} )
templateNames := make ( [ ] string , 0 , len ( templates ) )
templateByName := make ( map [ string ] codersdk . Template , len ( templates ) )
2022-04-06 17:42:40 +00:00
for _ , template := range templates {
2022-04-11 23:54:30 +00:00
templateName := template . Name
2022-04-14 17:23:20 +00:00
2022-04-11 23:54:30 +00:00
if template . WorkspaceOwnerCount > 0 {
developerText := "developer"
if template . WorkspaceOwnerCount != 1 {
developerText = "developers"
}
2022-04-14 17:23:20 +00:00
2022-04-11 23:54:30 +00:00
templateName += cliui . Styles . Placeholder . Render ( fmt . Sprintf ( " (used by %d %s)" , template . WorkspaceOwnerCount , developerText ) )
}
2022-04-14 17:23:20 +00:00
2022-04-11 23:54:30 +00:00
templateNames = append ( templateNames , templateName )
templateByName [ templateName ] = template
2022-03-22 19:17:50 +00:00
}
2022-04-14 17:23:20 +00:00
2022-03-22 19:17:50 +00:00
// Move the cursor up a single line for nicer display!
option , err := cliui . Select ( cmd , cliui . SelectOptions {
2022-04-06 17:42:40 +00:00
Options : templateNames ,
2022-03-22 19:17:50 +00:00
HideSearch : true ,
2022-02-12 19:34:04 +00:00
} )
if err != nil {
2022-03-22 19:17:50 +00:00
return err
}
2022-04-14 17:23:20 +00:00
2022-04-06 17:42:40 +00:00
template = templateByName [ option ]
2022-03-22 19:17:50 +00:00
} else {
2022-04-06 17:42:40 +00:00
template , err = client . TemplateByName ( cmd . Context ( ) , organization . ID , templateName )
2022-03-22 19:17:50 +00:00
if err != nil {
2022-04-06 17:42:40 +00:00
return xerrors . Errorf ( "get template by name: %w" , err )
2022-03-22 19:17:50 +00:00
}
2022-02-12 19:34:04 +00:00
}
2022-03-22 19:17:50 +00:00
2022-06-17 20:38:10 +00:00
var schedSpec * string
if startAt != "" {
sched , err := parseCLISchedule ( startAt )
if err != nil {
return err
}
schedSpec = ptr . Ref ( sched . String ( ) )
2022-06-07 12:37:45 +00:00
}
2022-04-06 17:42:40 +00:00
templateVersion , err := client . TemplateVersion ( cmd . Context ( ) , template . ActiveVersionID )
2022-02-12 19:34:04 +00:00
if err != nil {
return err
}
2022-04-06 17:42:40 +00:00
parameterSchemas , err := client . TemplateVersionSchema ( cmd . Context ( ) , templateVersion . ID )
2022-02-12 19:34:04 +00:00
if err != nil {
return err
}
2022-03-22 19:17:50 +00:00
2022-05-20 15:29:10 +00:00
// parameterMapFromFile can be nil if parameter file is not specified
var parameterMapFromFile map [ string ] string
if parameterFile != "" {
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( "Attempting to read the variables from the parameter file." ) + "\r\n" )
parameterMapFromFile , err = createParameterMapFromFile ( parameterFile )
if err != nil {
return err
}
}
disclaimerPrinted := false
2022-03-22 19:17:50 +00:00
parameters := make ( [ ] codersdk . CreateParameterRequest , 0 )
for _ , parameterSchema := range parameterSchemas {
if ! parameterSchema . AllowOverrideSource {
continue
}
2022-05-20 15:29:10 +00:00
if ! disclaimerPrinted {
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , cliui . Styles . Paragraph . Render ( "This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss)." ) + "\r\n" )
2022-05-20 15:29:10 +00:00
disclaimerPrinted = true
2022-03-22 19:17:50 +00:00
}
2022-05-20 15:29:10 +00:00
parameterValue , err := getParameterValueFromMapOrInput ( cmd , parameterMapFromFile , parameterSchema )
2022-03-22 19:17:50 +00:00
if err != nil {
return err
}
parameters = append ( parameters , codersdk . CreateParameterRequest {
Name : parameterSchema . Name ,
2022-05-20 15:29:10 +00:00
SourceValue : parameterValue ,
2022-05-19 18:04:44 +00:00
SourceScheme : codersdk . ParameterSourceSchemeData ,
DestinationScheme : parameterSchema . DefaultDestinationScheme ,
2022-03-22 19:17:50 +00:00
} )
}
2022-04-11 23:54:30 +00:00
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) )
2022-06-01 14:44:53 +00:00
// Run a dry-run with the given parameters to check correctness
after := time . Now ( )
dryRun , err := client . CreateTemplateVersionDryRun ( cmd . Context ( ) , templateVersion . ID , codersdk . CreateTemplateVersionDryRunRequest {
WorkspaceName : workspaceName ,
ParameterValues : parameters ,
} )
2022-02-12 19:34:04 +00:00
if err != nil {
2022-06-01 14:44:53 +00:00
return xerrors . Errorf ( "begin workspace dry-run: %w" , err )
}
_ , _ = fmt . Fprintln ( cmd . OutOrStdout ( ) , "Planning workspace..." )
err = cliui . ProvisionerJob ( cmd . Context ( ) , cmd . OutOrStdout ( ) , cliui . ProvisionerJobOptions {
Fetch : func ( ) ( codersdk . ProvisionerJob , error ) {
return client . TemplateVersionDryRun ( cmd . Context ( ) , templateVersion . ID , dryRun . ID )
} ,
Cancel : func ( ) error {
return client . CancelTemplateVersionDryRun ( cmd . Context ( ) , templateVersion . ID , dryRun . ID )
} ,
Logs : func ( ) ( <- chan codersdk . ProvisionerJobLog , error ) {
return client . TemplateVersionDryRunLogsAfter ( cmd . Context ( ) , templateVersion . ID , dryRun . ID , after )
} ,
// Don't show log output for the dry-run unless there's an error.
Silent : true ,
} )
if err != nil {
// TODO (Dean): reprompt for parameter values if we deem it to
// be a validation error
return xerrors . Errorf ( "dry-run workspace: %w" , err )
2022-02-12 19:34:04 +00:00
}
2022-06-01 14:44:53 +00:00
resources , err := client . TemplateVersionDryRunResources ( cmd . Context ( ) , templateVersion . ID , dryRun . ID )
if err != nil {
return xerrors . Errorf ( "get workspace dry-run resources: %w" , err )
}
2022-04-11 23:54:30 +00:00
err = cliui . WorkspaceResources ( cmd . OutOrStdout ( ) , resources , cliui . WorkspaceResourcesOptions {
WorkspaceName : workspaceName ,
// Since agent's haven't connected yet, hiding this makes more sense.
HideAgentState : true ,
Title : "Workspace Preview" ,
} )
2022-02-12 19:34:04 +00:00
if err != nil {
return err
}
2022-03-22 19:17:50 +00:00
_ , err = cliui . Prompt ( cmd , cliui . PromptOptions {
2022-04-11 23:54:30 +00:00
Text : "Confirm create?" ,
2022-02-12 19:34:04 +00:00
IsConfirm : true ,
} )
if err != nil {
return err
}
2022-04-25 21:11:03 +00:00
workspace , err := client . CreateWorkspace ( cmd . Context ( ) , organization . ID , codersdk . CreateWorkspaceRequest {
2022-05-23 22:31:41 +00:00
TemplateID : template . ID ,
Name : workspaceName ,
2022-06-07 12:37:45 +00:00
AutostartSchedule : schedSpec ,
2022-06-17 20:38:10 +00:00
TTLMillis : ptr . Ref ( stopAfter . Milliseconds ( ) ) ,
2022-05-23 22:31:41 +00:00
ParameterValues : parameters ,
2022-02-12 19:34:04 +00:00
} )
if err != nil {
return err
}
2022-04-14 17:23:20 +00:00
2022-06-01 14:44:53 +00:00
err = cliui . WorkspaceBuild ( cmd . Context ( ) , cmd . OutOrStdout ( ) , client , workspace . LatestBuild . ID , after )
2022-04-11 23:54:30 +00:00
if err != nil {
return err
}
2022-04-14 17:23:20 +00:00
2022-06-09 07:44:41 +00:00
_ , _ = fmt . Fprintf ( cmd . OutOrStdout ( ) , "\nThe %s workspace has been created!\n" , cliui . Styles . Keyword . Render ( workspace . Name ) )
2022-04-11 23:54:30 +00:00
return nil
2022-02-12 19:34:04 +00:00
} ,
}
2022-05-20 15:59:04 +00:00
cliui . AllowSkipPrompt ( cmd )
2022-04-14 17:23:20 +00:00
cliflag . StringVarP ( cmd . Flags ( ) , & templateName , "template" , "t" , "CODER_TEMPLATE_NAME" , "" , "Specify a template name." )
2022-05-20 15:29:10 +00:00
cliflag . StringVarP ( cmd . Flags ( ) , & parameterFile , "parameter-file" , "" , "CODER_PARAMETER_FILE" , "" , "Specify a file path with parameter values." )
2022-06-17 20:38:10 +00:00
cliflag . StringVarP ( cmd . Flags ( ) , & startAt , "start-at" , "" , "CODER_WORKSPACE_START_AT" , "" , "Specify the workspace autostart schedule. Check `coder schedule start --help` for the syntax." )
cliflag . DurationVarP ( cmd . Flags ( ) , & stopAfter , "stop-after" , "" , "CODER_WORKSPACE_STOP_AFTER" , 8 * time . Hour , "Specify a duration after which the workspace should shut down (e.g. 8h)." )
2022-02-12 19:34:04 +00:00
return cmd
}