@@ -15,18 +15,12 @@ import (
1515"testing"
1616"time"
1717
18- "github.com/prometheus/client_golang/prometheus"
19-
20- "github.com/coder/coder/v2/coderd/files"
21- agplprebuilds"github.com/coder/coder/v2/coderd/prebuilds"
22- "github.com/coder/coder/v2/enterprise/coderd/prebuilds"
23-
2418"github.com/google/uuid"
19+ "github.com/prometheus/client_golang/prometheus"
2520"github.com/stretchr/testify/assert"
2621"github.com/stretchr/testify/require"
2722
2823"cdr.dev/slog"
29-
3024"cdr.dev/slog/sloggers/slogtest"
3125
3226"github.com/coder/coder/v2/coderd/audit"
@@ -35,10 +29,13 @@ import (
3529"github.com/coder/coder/v2/coderd/database"
3630"github.com/coder/coder/v2/coderd/database/dbauthz"
3731"github.com/coder/coder/v2/coderd/database/dbfake"
32+ "github.com/coder/coder/v2/coderd/database/dbgen"
3833"github.com/coder/coder/v2/coderd/database/dbtestutil"
3934"github.com/coder/coder/v2/coderd/database/dbtime"
35+ "github.com/coder/coder/v2/coderd/files"
4036"github.com/coder/coder/v2/coderd/httpmw"
4137"github.com/coder/coder/v2/coderd/notifications"
38+ agplprebuilds"github.com/coder/coder/v2/coderd/prebuilds"
4239"github.com/coder/coder/v2/coderd/provisionerdserver"
4340"github.com/coder/coder/v2/coderd/rbac"
4441"github.com/coder/coder/v2/coderd/rbac/policy"
@@ -50,6 +47,7 @@ import (
5047"github.com/coder/coder/v2/enterprise/audit/backends"
5148"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
5249"github.com/coder/coder/v2/enterprise/coderd/license"
50+ "github.com/coder/coder/v2/enterprise/coderd/prebuilds"
5351"github.com/coder/coder/v2/enterprise/coderd/schedule"
5452"github.com/coder/coder/v2/provisioner/echo"
5553"github.com/coder/coder/v2/provisionersdk"
@@ -2519,6 +2517,185 @@ func templateWithFailedResponseAndPresetsWithPrebuilds(desiredInstances int32) *
25192517}
25202518}
25212519
2520+ func TestPrebuildUpdateLifecycleParams (t * testing.T ) {
2521+ t .Parallel ()
2522+
2523+ // Autostart schedule configuration set to weekly at 9:30 AM UTC
2524+ autostartSchedule ,err := cron .Weekly ("CRON_TZ=UTC 30 9 * * 1-5" )
2525+ require .NoError (t ,err )
2526+
2527+ // TTL configuration set to 8 hours
2528+ ttlMillis := ptr .Ref ((8 * time .Hour ).Milliseconds ())
2529+
2530+ // Deadline configuration set to January 1st, 2024 at 10:00 AM UTC
2531+ deadline := time .Date (2024 ,1 ,1 ,10 ,0 ,0 ,0 ,time .UTC )
2532+
2533+ cases := []struct {
2534+ name string
2535+ endpoint func (* testing.T , context.Context ,* codersdk.Client , uuid.UUID )error
2536+ apiErrorMsg string
2537+ assertUpdate func (* testing.T ,* quartz.Mock ,* codersdk.Client , uuid.UUID )
2538+ }{
2539+ {
2540+ name :"AutostartUpdatePrebuildAfterClaim" ,
2541+ endpoint :func (t * testing.T ,ctx context.Context ,client * codersdk.Client ,workspaceID uuid.UUID )error {
2542+ err = client .UpdateWorkspaceAutostart (ctx ,workspaceID , codersdk.UpdateWorkspaceAutostartRequest {
2543+ Schedule :ptr .Ref (autostartSchedule .String ()),
2544+ })
2545+ return err
2546+ },
2547+ apiErrorMsg :"Autostart is not supported for prebuilt workspaces" ,
2548+ assertUpdate :func (t * testing.T ,clock * quartz.Mock ,client * codersdk.Client ,workspaceID uuid.UUID ) {
2549+ // The workspace's autostart schedule should be updated to the given schedule,
2550+ // and its next start time should be set to 2024-01-01 09:30 AM UTC
2551+ updatedWorkspace := coderdtest .MustWorkspace (t ,client ,workspaceID )
2552+ require .Equal (t ,autostartSchedule .String (),* updatedWorkspace .AutostartSchedule )
2553+ require .Equal (t ,autostartSchedule .Next (clock .Now ()),updatedWorkspace .NextStartAt .UTC ())
2554+ expectedNext := time .Date (2024 ,1 ,1 ,9 ,30 ,0 ,0 ,time .UTC )
2555+ require .Equal (t ,expectedNext ,updatedWorkspace .NextStartAt .UTC ())
2556+ },
2557+ },
2558+ {
2559+ name :"TTLUpdatePrebuildAfterClaim" ,
2560+ endpoint :func (t * testing.T ,ctx context.Context ,client * codersdk.Client ,workspaceID uuid.UUID )error {
2561+ err := client .UpdateWorkspaceTTL (ctx ,workspaceID , codersdk.UpdateWorkspaceTTLRequest {
2562+ TTLMillis :ttlMillis ,
2563+ })
2564+ return err
2565+ },
2566+ apiErrorMsg :"TTL updates are not supported for prebuilt workspaces" ,
2567+ assertUpdate :func (t * testing.T ,clock * quartz.Mock ,client * codersdk.Client ,workspaceID uuid.UUID ) {
2568+ // The workspace's TTL should be updated accordingly
2569+ updatedWorkspace := coderdtest .MustWorkspace (t ,client ,workspaceID )
2570+ require .Equal (t ,ttlMillis ,updatedWorkspace .TTLMillis )
2571+ },
2572+ },
2573+ {
2574+ name :"DormantUpdatePrebuildAfterClaim" ,
2575+ endpoint :func (t * testing.T ,ctx context.Context ,client * codersdk.Client ,workspaceID uuid.UUID )error {
2576+ err := client .UpdateWorkspaceDormancy (ctx ,workspaceID , codersdk.UpdateWorkspaceDormancy {
2577+ Dormant :true ,
2578+ })
2579+ return err
2580+ },
2581+ apiErrorMsg :"Dormancy updates are not supported for prebuilt workspaces" ,
2582+ assertUpdate :func (t * testing.T ,clock * quartz.Mock ,client * codersdk.Client ,workspaceID uuid.UUID ) {
2583+ // The workspace's dormantAt should be updated accordingly
2584+ updatedWorkspace := coderdtest .MustWorkspace (t ,client ,workspaceID )
2585+ require .Equal (t ,clock .Now (),updatedWorkspace .DormantAt .UTC ())
2586+ },
2587+ },
2588+ {
2589+ name :"DeadlineUpdatePrebuildAfterClaim" ,
2590+ endpoint :func (t * testing.T ,ctx context.Context ,client * codersdk.Client ,workspaceID uuid.UUID )error {
2591+ err := client .PutExtendWorkspace (ctx ,workspaceID , codersdk.PutExtendWorkspaceRequest {
2592+ Deadline :deadline ,
2593+ })
2594+ return err
2595+ },
2596+ apiErrorMsg :"Deadline extension is not supported for prebuilt workspaces" ,
2597+ assertUpdate :func (t * testing.T ,clock * quartz.Mock ,client * codersdk.Client ,workspaceID uuid.UUID ) {
2598+ // The workspace build's deadline should be updated accordingly
2599+ updatedWorkspace := coderdtest .MustWorkspace (t ,client ,workspaceID )
2600+ require .Equal (t ,deadline ,updatedWorkspace .LatestBuild .Deadline .Time .UTC ())
2601+ },
2602+ },
2603+ }
2604+
2605+ for _ ,tc := range cases {
2606+ tc := tc
2607+ t .Run (tc .name ,func (t * testing.T ) {
2608+ t .Parallel ()
2609+
2610+ // Set the clock to Monday, January 1st, 2024 at 8:00 AM UTC to keep the test deterministic
2611+ clock := quartz .NewMock (t )
2612+ clock .Set (time .Date (2024 ,1 ,1 ,8 ,0 ,0 ,0 ,time .UTC ))
2613+
2614+ // Setup
2615+ client ,db ,owner := coderdenttest .NewWithDatabase (t ,& coderdenttest.Options {
2616+ Options :& coderdtest.Options {
2617+ IncludeProvisionerDaemon :true ,
2618+ Clock :clock ,
2619+ },
2620+ LicenseOptions :& coderdenttest.LicenseOptions {
2621+ Features : license.Features {
2622+ codersdk .FeatureWorkspacePrebuilds :1 ,
2623+ },
2624+ },
2625+ })
2626+
2627+ // Given: a template and a template version with preset and a prebuilt workspace
2628+ presetID := uuid .New ()
2629+ version := coderdtest .CreateTemplateVersion (t ,client ,owner .OrganizationID ,nil )
2630+ _ = coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
2631+ template := coderdtest .CreateTemplate (t ,client ,owner .OrganizationID ,version .ID )
2632+ dbgen .Preset (t ,db , database.InsertPresetParams {
2633+ ID :presetID ,
2634+ TemplateVersionID :version .ID ,
2635+ DesiredInstances : sql.NullInt32 {Int32 :1 ,Valid :true },
2636+ })
2637+ workspaceBuild := dbfake .WorkspaceBuild (t ,db , database.WorkspaceTable {
2638+ OwnerID :database .PrebuildsSystemUserID ,
2639+ TemplateID :template .ID ,
2640+ }).Seed (database.WorkspaceBuild {
2641+ TemplateVersionID :version .ID ,
2642+ TemplateVersionPresetID : uuid.NullUUID {
2643+ UUID :presetID ,
2644+ Valid :true ,
2645+ },
2646+ }).WithAgent (func (agent []* proto.Agent ) []* proto.Agent {
2647+ return agent
2648+ }).Do ()
2649+
2650+ // Mark the prebuilt workspace's agent as ready so the prebuild can be claimed
2651+ // nolint:gocritic
2652+ ctx := dbauthz .AsSystemRestricted (testutil .Context (t ,testutil .WaitLong ))
2653+ agent ,err := db .GetWorkspaceAgentAndLatestBuildByAuthToken (ctx ,uuid .MustParse (workspaceBuild .AgentToken ))
2654+ require .NoError (t ,err )
2655+ err = db .UpdateWorkspaceAgentLifecycleStateByID (ctx , database.UpdateWorkspaceAgentLifecycleStateByIDParams {
2656+ ID :agent .WorkspaceAgent .ID ,
2657+ LifecycleState :database .WorkspaceAgentLifecycleStateReady ,
2658+ })
2659+ require .NoError (t ,err )
2660+
2661+ // Given: a prebuilt workspace
2662+ prebuild := coderdtest .MustWorkspace (t ,client ,workspaceBuild .Workspace .ID )
2663+
2664+ // When: the lifecycle-update endpoint is called for the prebuilt workspace
2665+ err = tc .endpoint (t ,ctx ,client ,prebuild .ID )
2666+
2667+ // Then: a 409 Conflict should be returned, with an error message specific to the lifecycle parameter
2668+ var apiErr * codersdk.Error
2669+ require .ErrorAs (t ,err ,& apiErr )
2670+ require .Equal (t ,http .StatusConflict ,apiErr .StatusCode ())
2671+ require .Equal (t ,tc .apiErrorMsg ,apiErr .Response .Message )
2672+
2673+ // Given: the prebuilt workspace is claimed by a user
2674+ user ,err := client .User (ctx ,"testUser" )
2675+ require .NoError (t ,err )
2676+ claimedWorkspace ,err := client .CreateUserWorkspace (ctx ,user .ID .String (), codersdk.CreateWorkspaceRequest {
2677+ TemplateVersionID :version .ID ,
2678+ TemplateVersionPresetID :presetID ,
2679+ Name :coderdtest .RandomUsername (t ),
2680+ // The 'extend' endpoint requires the workspace to have an existing deadline.
2681+ // To ensure this, we set the workspace's TTL to 1 hour.
2682+ TTLMillis : ptr.Ref [int64 ](time .Hour .Milliseconds ()),
2683+ })
2684+ require .NoError (t ,err )
2685+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,claimedWorkspace .LatestBuild .ID )
2686+ workspace := coderdtest .MustWorkspace (t ,client ,claimedWorkspace .ID )
2687+ require .Equal (t ,prebuild .ID ,workspace .ID )
2688+
2689+ // When: the same lifecycle-update endpoint is called for the claimed workspace
2690+ err = tc .endpoint (t ,ctx ,client ,workspace .ID )
2691+ require .NoError (t ,err )
2692+
2693+ // Then: the workspace's lifecycle parameter should be updated accordingly
2694+ tc .assertUpdate (t ,clock ,client ,claimedWorkspace .ID )
2695+ })
2696+ }
2697+ }
2698+
25222699// TestWorkspaceTemplateParamsChange tests a workspace with a parameter that
25232700// validation changes on apply. The params used in create workspace are invalid
25242701// according to the static params on import.