@@ -2,17 +2,30 @@ package cli_test
2
2
3
3
import (
4
4
"bytes"
5
+ "database/sql"
5
6
"net/http"
6
7
"testing"
8
+ "time"
7
9
10
+ "github.com/google/uuid"
8
11
"github.com/stretchr/testify/assert"
9
12
"github.com/stretchr/testify/require"
10
13
11
14
"github.com/coder/coder/v2/cli/clitest"
12
15
"github.com/coder/coder/v2/coderd/coderdtest"
16
+ "github.com/coder/coder/v2/coderd/database"
17
+ "github.com/coder/coder/v2/coderd/database/dbauthz"
18
+ "github.com/coder/coder/v2/coderd/database/dbfake"
19
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
20
+ "github.com/coder/coder/v2/coderd/database/dbtime"
21
+ "github.com/coder/coder/v2/coderd/util/ptr"
13
22
"github.com/coder/coder/v2/codersdk"
14
23
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
15
24
"github.com/coder/coder/v2/enterprise/coderd/license"
25
+ "github.com/coder/coder/v2/provisionersdk/proto"
26
+ "github.com/coder/coder/v2/pty/ptytest"
27
+ "github.com/coder/coder/v2/testutil"
28
+ "github.com/coder/quartz"
16
29
)
17
30
18
31
func TestPrebuildsPause (t * testing.T ) {
@@ -341,3 +354,144 @@ func TestPrebuildsSettingsAPI(t *testing.T) {
341
354
assert .False (t , settings .ReconciliationPaused )
342
355
})
343
356
}
357
+
358
+ // TestSchedulePrebuilds verifies the CLI schedule command when used with prebuilds.
359
+ // Running the command on an unclaimed prebuild fails, but after the prebuild is
360
+ // claimed (becoming a regular workspace) it succeeds as expected.
361
+ func TestSchedulePrebuilds (t * testing.T ) {
362
+ t .Parallel ()
363
+
364
+ if ! dbtestutil .WillUsePostgres () {
365
+ t .Skip ("this test requires postgres" )
366
+ }
367
+
368
+ // Setup
369
+ clock := quartz .NewMock (t )
370
+ clock .Set (dbtime .Now ())
371
+ client , db , owner := coderdenttest .NewWithDatabase (t , & coderdenttest.Options {
372
+ Options : & coderdtest.Options {
373
+ DeploymentValues : coderdtest .DeploymentValues (t ),
374
+ IncludeProvisionerDaemon : true ,
375
+ Clock : clock ,
376
+ },
377
+ LicenseOptions : & coderdenttest.LicenseOptions {
378
+ Features : license.Features {
379
+ codersdk .FeatureWorkspacePrebuilds : 1 ,
380
+ },
381
+ },
382
+ })
383
+
384
+ cases := []struct {
385
+ name string
386
+ cliErrorMsg string
387
+ cmdArgs func (string ) []string
388
+ }{
389
+ {
390
+ name : "Autostart" ,
391
+ cliErrorMsg : "autostart configuration is not supported for prebuilt workspaces" ,
392
+ cmdArgs : func (workspaceName string ) []string {
393
+ return []string {"schedule" , "start" , workspaceName , "7:30AM" , "Mon-Fri" , "Europe/Lisbon" }
394
+ },
395
+ },
396
+ {
397
+ name : "Autostop" ,
398
+ cliErrorMsg : "autostop configuration is not supported for prebuilt workspaces" ,
399
+ cmdArgs : func (workspaceName string ) []string {
400
+ return []string {"schedule" , "stop" , workspaceName , "8h30m" }
401
+ },
402
+ },
403
+ //{
404
+ // name: "Extend",
405
+ // cliErrorMsg: "extend configuration is not supported for prebuilt workspaces",
406
+ // cmdArgs: func(workspaceName string) []string {
407
+ // return []string{"schedule", "extend", workspaceName, "90m"}
408
+ // },
409
+ // },
410
+ }
411
+
412
+ for _ , tc := range cases {
413
+ tc := tc
414
+ t .Run (tc .name , func (t * testing.T ) {
415
+ t .Parallel ()
416
+
417
+ // Given: a template and a template version with preset and a prebuilt workspace
418
+ presetID := uuid .New ()
419
+ tv := dbfake .TemplateVersion (t , db ).Seed (database.TemplateVersion {
420
+ OrganizationID : owner .OrganizationID ,
421
+ CreatedBy : owner .UserID ,
422
+ }).Preset (database.TemplateVersionPreset {
423
+ ID : presetID ,
424
+ DesiredInstances : sql.NullInt32 {
425
+ Int32 : 1 ,
426
+ Valid : true ,
427
+ },
428
+ }).Do ()
429
+
430
+ workspaceBuild := dbfake .WorkspaceBuild (t , db , database.WorkspaceTable {
431
+ OwnerID : database .PrebuildsSystemUserID ,
432
+ TemplateID : tv .Template .ID ,
433
+ }).Seed (database.WorkspaceBuild {
434
+ TemplateVersionID : tv .TemplateVersion .ID ,
435
+ TemplateVersionPresetID : uuid.NullUUID {
436
+ UUID : presetID ,
437
+ Valid : true ,
438
+ },
439
+ Deadline : clock .Now ().Add (time .Hour ),
440
+ }).WithAgent (func (agent []* proto.Agent ) []* proto.Agent {
441
+ return agent
442
+ }).Do ()
443
+
444
+ // nolint:gocritic
445
+ ctx := dbauthz .AsSystemRestricted (testutil .Context (t , testutil .WaitLong ))
446
+ agent , err := db .GetWorkspaceAgentAndLatestBuildByAuthToken (ctx , uuid .MustParse (workspaceBuild .AgentToken ))
447
+ require .NoError (t , err )
448
+ err = db .UpdateWorkspaceAgentLifecycleStateByID (ctx , database.UpdateWorkspaceAgentLifecycleStateByIDParams {
449
+ ID : agent .WorkspaceAgent .ID ,
450
+ LifecycleState : database .WorkspaceAgentLifecycleStateReady ,
451
+ })
452
+ require .NoError (t , err )
453
+ prebuild := coderdtest .MustWorkspace (t , client , workspaceBuild .Workspace .ID )
454
+
455
+ // When: running the schedule command over a prebuilt workspace
456
+ inv , root := clitest .New (t , tc .cmdArgs (prebuild .OwnerName + "/" + prebuild .Name )... )
457
+ clitest .SetupConfig (t , client , root )
458
+ ptytest .New (t ).Attach (inv )
459
+ doneChan := make (chan struct {})
460
+ var runErr error
461
+ go func () {
462
+ defer close (doneChan )
463
+ runErr = inv .Run ()
464
+ }()
465
+ <- doneChan
466
+
467
+ // Then: return an error
468
+ require .Error (t , runErr )
469
+ require .Contains (t , runErr .Error (), tc .cliErrorMsg )
470
+
471
+ // Given: a user claims the prebuilt workspace
472
+ user , err := client .User (ctx , "testUser" )
473
+ require .NoError (t , err )
474
+ claimedWorkspace , err := client .CreateUserWorkspace (ctx , user .ID .String (), codersdk.CreateWorkspaceRequest {
475
+ TemplateVersionID : tv .TemplateVersion .ID ,
476
+ TemplateVersionPresetID : presetID ,
477
+ Name : coderdtest .RandomUsername (t ),
478
+ // The 'extend' command requires the workspace to have an existing deadline.
479
+ // To ensure this, we set the workspace's TTL to 1 hour.
480
+ TTLMillis : ptr.Ref [int64 ](3600000 ),
481
+ })
482
+ require .NoError (t , err )
483
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t , client , claimedWorkspace .LatestBuild .ID )
484
+ workspace := coderdtest .MustWorkspace (t , client , claimedWorkspace .ID )
485
+ require .Equal (t , prebuild .ID , workspace .ID )
486
+
487
+ // When: running the schedule command over the claimed workspace
488
+ inv , root = clitest .New (t , tc .cmdArgs (workspace .OwnerName + "/" + workspace .Name )... )
489
+ clitest .SetupConfig (t , client , root )
490
+ pty := ptytest .New (t ).Attach (inv )
491
+ require .NoError (t , inv .Run ())
492
+
493
+ // Then: the updated schedule should be shown
494
+ pty .ExpectMatch (workspace .OwnerName + "/" + workspace .Name )
495
+ })
496
+ }
497
+ }
0 commit comments