@@ -44,7 +44,8 @@ func workspacesCmd() *cobra.Command {
4444 watchBuildLogCommand (),
4545 rebuildWorkspaceCommand (),
4646 createWorkspaceCmd (),
47- createWorkspaceFromConfigCmd (),
47+ workspaceFromConfigCmd (true ),
48+ workspaceFromConfigCmd (false ),
4849 editWorkspaceCmd (),
4950 )
5051 return cmd
@@ -296,130 +297,195 @@ coder workspaces create my-new-powerful-workspace --cpu 12 --disk 100 --memory 1
296297 return cmd
297298}
298299
299- func createWorkspaceFromConfigCmd () * cobra.Command {
300+ // selectOrg finds the organization in the list or returns the default organization
301+ // if the needle isn't found.
302+ func selectOrg (needle string , haystack []coder.Organization ) (* coder.Organization , error ) {
303+ var userOrg * coder.Organization
304+ for i := range haystack {
305+ // Look for org by name
306+ if haystack [i ].Name == needle {
307+ userOrg = & haystack [i ]
308+ break
309+ }
310+ // Or use default if the provided is blank
311+ if needle == "" && haystack [i ].Default {
312+ userOrg = & haystack [i ]
313+ break
314+ }
315+ }
316+
317+ if userOrg == nil {
318+ if needle != "" {
319+ return nil , xerrors .Errorf ("Unable to locate org '%s'" , needle )
320+ }
321+ return nil , xerrors .Errorf ("Unable to locate a default organization for the user" )
322+ }
323+ return userOrg , nil
324+ }
325+
326+ // workspaceFromConfigCmd will return a create or an update workspace for a template'd workspace.
327+ // The code for create/update is nearly identical.
328+ // If `update` is true, the update command is returned. If false, the create command.
329+ func workspaceFromConfigCmd (update bool ) * cobra.Command {
300330 var (
301- ref string
302- repo string
303- follow bool
304- filepath string
305- org string
306- providerName string
307- workspaceName string
331+ ref string
332+ repo string
333+ follow bool
334+ filepath string
335+ org string
336+ providerName string
337+ envName string
308338 )
309339
310- cmd := & cobra.Command {
311- Use : "create-from-config" ,
312- Short : "create a new workspace from a template" ,
313- Long : "Create a new Coder workspace using a Workspaces As Code template." ,
314- Example : `# create a new workspace from git repository
315- coder workspaces create-from-config --name="dev-workspace" --repo-url https://github.com/cdr/m --ref my-branch
316- coder workspaces create-from-config --name="dev-workspace" -f coder.yaml` ,
317- RunE : func (cmd * cobra.Command , args []string ) error {
318- ctx := cmd .Context ()
340+ run := func (cmd * cobra.Command , args []string ) error {
341+ ctx := cmd .Context ()
319342
320- if workspaceName == "" {
321- return clog .Error ("Must provide a workspace name." ,
322- clog .BlankLine ,
323- clog .Tipf ("Use --name=<workspace-name> to name your workspace" ),
324- )
325- }
343+ // Update requires the env name, and the name should be the first argument.
344+ if update {
345+ envName = args [0 ]
346+ } else if envName == "" {
347+ // Create takes the name as a flag, and it must be set
348+ return clog .Error ("Must provide a workspace name." ,
349+ clog .BlankLine ,
350+ clog .Tipf ("Use --name=<workspace-name> to name your workspace" ),
351+ )
352+ }
326353
327- client , err := newClient (ctx , true )
354+ client , err := newClient (ctx , true )
355+ if err != nil {
356+ return err
357+ }
358+
359+ orgs , err := getUserOrgs (ctx , client , coder .Me )
360+ if err != nil {
361+ return err
362+ }
363+
364+ multiOrgMember := len (orgs ) > 1
365+ if multiOrgMember && org == "" {
366+ return xerrors .New ("org is required for multi-org members" )
367+ }
368+
369+ // This is the env to be updated/created
370+ var env * coder.Workspace
371+
372+ // OrgID is the org where the template and env should be created.
373+ // If we are updating an env, use the orgID from the workspace.
374+ var orgID string
375+ if update {
376+ env , err = findWorkspace (ctx , client , envName , coder .Me )
328377 if err != nil {
329- return err
378+ return handleAPIError ( err )
330379 }
331-
332- orgs , err := getUserOrgs (ctx , client , coder .Me )
380+ orgID = env .OrganizationID
381+ } else {
382+ var userOrg * coder.Organization
383+ // Select org in list or use default
384+ userOrg , err := selectOrg (org , orgs )
333385 if err != nil {
334386 return err
335387 }
336388
337- multiOrgMember := len (orgs ) > 1
338- if multiOrgMember && org == "" {
339- return xerrors .New ("org is required for multi-org members" )
340- }
341-
342- var userOrg * coder.Organization
343- for i := range orgs {
344- // Look for org by name
345- if orgs [i ].Name == org {
346- userOrg = & orgs [i ]
347- break
348- }
349- // Or use default if the provided is blank
350- if org == "" && orgs [i ].Default {
351- userOrg = & orgs [i ]
352- break
353- }
354- }
389+ orgID = userOrg .ID
390+ }
355391
356- if userOrg == nil {
357- if org != "" {
358- return xerrors .Errorf ("Unable to locate org '%s'" , org )
359- }
360- return xerrors .Errorf ("Unable to locate a default organization for the user" )
361- }
392+ if filepath == "" && ref == "" && repo == "" {
393+ return clog .Error ("Must specify a configuration source" ,
394+ "A template source is either sourced from a local file (-f) or from a git repository (--repo-url and --ref)" ,
395+ )
396+ }
362397
363- var rd io.Reader
364- if filepath != "" {
365- b , err := ioutil .ReadFile (filepath )
366- if err != nil {
367- return xerrors .Errorf ("read local file: %w" , err )
368- }
369- rd = bytes .NewReader (b )
398+ var rd io.Reader
399+ if filepath != "" {
400+ b , err := ioutil .ReadFile (filepath )
401+ if err != nil {
402+ return xerrors .Errorf ("read local file: %w" , err )
370403 }
404+ rd = bytes .NewReader (b )
405+ }
371406
372- req := coder.ParseTemplateRequest {
373- RepoURL : repo ,
374- Ref : ref ,
375- Local : rd ,
376- OrgID : userOrg . ID ,
377- Filepath : ".coder/coder.yaml" ,
378- }
407+ req := coder.ParseTemplateRequest {
408+ RepoURL : repo ,
409+ Ref : ref ,
410+ Local : rd ,
411+ OrgID : orgID ,
412+ Filepath : ".coder/coder.yaml" ,
413+ }
379414
380- version , err := client .ParseTemplate (ctx , req )
381- if err != nil {
382- return handleAPIError (err )
383- }
415+ version , err := client .ParseTemplate (ctx , req )
416+ if err != nil {
417+ return handleAPIError (err )
418+ }
384419
385- provider , err := coderutil .DefaultWorkspaceProvider (ctx , client )
386- if err != nil {
387- return xerrors .Errorf ("default workspace provider: %w" , err )
388- }
420+ provider , err := coderutil .DefaultWorkspaceProvider (ctx , client )
421+ if err != nil {
422+ return xerrors .Errorf ("default workspace provider: %w" , err )
423+ }
389424
390- workspace , err := client .CreateWorkspace (ctx , coder.CreateWorkspaceRequest {
391- OrgID : userOrg .ID ,
425+ if update {
426+ err = client .EditWorkspace (ctx , env .ID , coder.UpdateWorkspaceReq {
427+ TemplateID : & version .TemplateID ,
428+ })
429+ } else {
430+ env , err = client .CreateWorkspace (ctx , coder.CreateWorkspaceRequest {
431+ OrgID : orgID ,
392432 TemplateID : version .TemplateID ,
393433 ResourcePoolID : provider .ID ,
394434 Namespace : provider .DefaultNamespace ,
395- Name : workspaceName ,
435+ Name : envName ,
396436 })
397- if err != nil {
398- return handleAPIError (err )
399- }
437+ }
438+ if err != nil {
439+ return handleAPIError (err )
440+ }
400441
401- if follow {
402- clog .LogSuccess ("creating workspace..." )
403- if err := trailBuildLogs (ctx , client , workspace .ID ); err != nil {
404- return err
405- }
406- return nil
442+ if follow {
443+ clog .LogSuccess ("creating workspace..." )
444+ if err := trailBuildLogs (ctx , client , env .ID ); err != nil {
445+ return err
407446 }
408-
409- clog .LogSuccess ("creating workspace..." ,
410- clog .BlankLine ,
411- clog .Tipf (`run "coder workspaces watch-build %s" to trail the build logs` , workspace .Name ),
412- )
413447 return nil
414- },
448+ }
449+
450+ clog .LogSuccess ("creating workspace..." ,
451+ clog .BlankLine ,
452+ clog .Tipf (`run "coder envs watch-build %s" to trail the build logs` , env .Name ),
453+ )
454+ return nil
415455 }
416- cmd .Flags ().StringVarP (& org , "org" , "o" , "" , "name of the organization the workspace should be created under." )
456+
457+ var cmd * cobra.Command
458+ if update {
459+ cmd = & cobra.Command {
460+ Use : "edit-from-config" ,
461+ Short : "change the template a workspace is tracking" ,
462+ Long : "Edit an existing Coder workspace using a Workspaces As Code template." ,
463+ Args : cobra .ExactArgs (1 ),
464+ Example : `# edit a new workspace from git repository
465+ coder envs edit-from-config dev-env --repo-url https://github.com/cdr/m --ref my-branch
466+ coder envs edit-from-config dev-env -f coder.yaml` ,
467+ RunE : run ,
468+ }
469+ } else {
470+ cmd = & cobra.Command {
471+ Use : "create-from-config" ,
472+ Short : "create a new workspace from a template" ,
473+ Long : "Create a new Coder workspace using a Workspaces As Code template." ,
474+ Example : `# create a new workspace from git repository
475+ coder envs create-from-config --name="dev-env" --repo-url https://github.com/cdr/m --ref my-branch
476+ coder envs create-from-config --name="dev-env" -f coder.yaml` ,
477+ RunE : run ,
478+ }
479+ cmd .Flags ().StringVar (& providerName , "provider" , "" , "name of Workspace Provider with which to create the workspace" )
480+ cmd .Flags ().StringVar (& envName , "name" , "" , "name of the workspace to be created" )
481+ cmd .Flags ().StringVarP (& org , "org" , "o" , "" , "name of the organization the workspace should be created under." )
482+ // Ref and repo-url can only be used for create
483+ cmd .Flags ().StringVarP (& ref , "ref" , "" , "master" , "git reference to pull template from. May be a branch, tag, or commit hash." )
484+ cmd .Flags ().StringVarP (& repo , "repo-url" , "r" , "" , "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'." )
485+ }
486+
417487 cmd .Flags ().StringVarP (& filepath , "filepath" , "f" , "" , "path to local template file." )
418- cmd .Flags ().StringVarP (& ref , "ref" , "" , "master" , "git reference to pull template from. May be a branch, tag, or commit hash." )
419- cmd .Flags ().StringVarP (& repo , "repo-url" , "r" , "" , "URL of the git repository to pull the config from. Config file must live in '.coder/coder.yaml'." )
420488 cmd .Flags ().BoolVar (& follow , "follow" , false , "follow buildlog after initiating rebuild" )
421- cmd .Flags ().StringVar (& providerName , "provider" , "" , "name of Workspace Provider with which to create the workspace" )
422- cmd .Flags ().StringVar (& workspaceName , "name" , "" , "name of the workspace to be created" )
423489 return cmd
424490}
425491
0 commit comments