@@ -44,7 +44,8 @@ func workspacesCmd() *cobra.Command {
44
44
watchBuildLogCommand (),
45
45
rebuildWorkspaceCommand (),
46
46
createWorkspaceCmd (),
47
- createWorkspaceFromConfigCmd (),
47
+ workspaceFromConfigCmd (true ),
48
+ workspaceFromConfigCmd (false ),
48
49
editWorkspaceCmd (),
49
50
)
50
51
return cmd
@@ -296,130 +297,195 @@ coder workspaces create my-new-powerful-workspace --cpu 12 --disk 100 --memory 1
296
297
return cmd
297
298
}
298
299
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 {
300
330
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
308
338
)
309
339
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 ()
319
342
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
+ }
326
353
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 )
328
377
if err != nil {
329
- return err
378
+ return handleAPIError ( err )
330
379
}
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 )
333
385
if err != nil {
334
386
return err
335
387
}
336
388
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
+ }
355
391
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
+ }
362
397
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 )
370
403
}
404
+ rd = bytes .NewReader (b )
405
+ }
371
406
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
+ }
379
414
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
+ }
384
419
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
+ }
389
424
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 ,
392
432
TemplateID : version .TemplateID ,
393
433
ResourcePoolID : provider .ID ,
394
434
Namespace : provider .DefaultNamespace ,
395
- Name : workspaceName ,
435
+ Name : envName ,
396
436
})
397
- if err != nil {
398
- return handleAPIError (err )
399
- }
437
+ }
438
+ if err != nil {
439
+ return handleAPIError (err )
440
+ }
400
441
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
407
446
}
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
- )
413
447
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
415
455
}
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
+
417
487
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'." )
420
488
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" )
423
489
return cmd
424
490
}
425
491
0 commit comments