@@ -392,6 +392,147 @@ func mustCreateAgentLogs(ctx context.Context, t *testing.T, db database.Store, a
392392 require .NotEmpty (t , agentLogs , "agent logs must be present" )
393393}
394394
395+ func TestDeleteOldWorkspaceAgentLogsRetention (t * testing.T ) {
396+ t .Parallel ()
397+
398+ t .Run ("CustomRetention" , func (t * testing.T ) {
399+ t .Parallel ()
400+
401+ ctx := testutil .Context (t , testutil .WaitShort )
402+ clk := quartz .NewMock (t )
403+ now := dbtime .Now ()
404+ retentionPeriod := 30 * 24 * time .Hour
405+ threshold := now .Add (- retentionPeriod )
406+ beforeThreshold := threshold .Add (- 24 * time .Hour ) // 31 days ago
407+ afterThreshold := threshold .Add (24 * time .Hour ) // 29 days ago
408+ clk .Set (now ).MustWait (ctx )
409+
410+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
411+ org := dbgen .Organization (t , db , database.Organization {})
412+ user := dbgen .User (t , db , database.User {})
413+ _ = dbgen .OrganizationMember (t , db , database.OrganizationMember {UserID : user .ID , OrganizationID : org .ID })
414+ tv := dbgen .TemplateVersion (t , db , database.TemplateVersion {OrganizationID : org .ID , CreatedBy : user .ID })
415+ tmpl := dbgen .Template (t , db , database.Template {OrganizationID : org .ID , ActiveVersionID : tv .ID , CreatedBy : user .ID })
416+
417+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
418+
419+ // Workspace with two builds, both before the 30-day threshold.
420+ ws := dbgen .Workspace (t , db , database.WorkspaceTable {Name : "test-ws" , OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
421+ wb1 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , beforeThreshold , 1 )
422+ wb2 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , beforeThreshold , 2 )
423+ agent1 := mustCreateAgent (t , db , wb1 )
424+ agent2 := mustCreateAgent (t , db , wb2 )
425+ mustCreateAgentLogs (ctx , t , db , agent1 , & beforeThreshold , "agent 1 logs should be deleted" )
426+ mustCreateAgentLogs (ctx , t , db , agent2 , & beforeThreshold , "agent 2 logs should be retained" )
427+
428+ // Workspace with build after the 30-day threshold.
429+ wsRecent := dbgen .Workspace (t , db , database.WorkspaceTable {Name : "recent-ws" , OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
430+ wbRecent := mustCreateWorkspaceBuild (t , db , org , tv , wsRecent .ID , afterThreshold , 1 )
431+ agentRecent := mustCreateAgent (t , db , wbRecent )
432+ mustCreateAgentLogs (ctx , t , db , agentRecent , & afterThreshold , "recent agent logs should be retained" )
433+
434+ done := awaitDoTick (ctx , t , clk )
435+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
436+ Retention : codersdk.RetentionConfig {
437+ WorkspaceAgentLogs : serpent .Duration (retentionPeriod ),
438+ },
439+ }, clk )
440+ defer closer .Close ()
441+ testutil .TryReceive (ctx , t , done )
442+
443+ // Agent 1 logs should be deleted (non-latest build, older than 30 days).
444+ assertNoWorkspaceAgentLogs (ctx , t , db , agent1 .ID )
445+ // Agent 2 logs should be retained (latest build).
446+ assertWorkspaceAgentLogs (ctx , t , db , agent2 .ID , "agent 2 logs should be retained" )
447+ // Recent agent logs should be retained (within 30-day threshold).
448+ assertWorkspaceAgentLogs (ctx , t , db , agentRecent .ID , "recent agent logs should be retained" )
449+ })
450+
451+ t .Run ("RetentionDisabled" , func (t * testing.T ) {
452+ t .Parallel ()
453+
454+ ctx := testutil .Context (t , testutil .WaitShort )
455+ clk := quartz .NewMock (t )
456+ now := dbtime .Now ()
457+ veryOld := now .Add (- 60 * 24 * time .Hour )
458+ clk .Set (now ).MustWait (ctx )
459+
460+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
461+ org := dbgen .Organization (t , db , database.Organization {})
462+ user := dbgen .User (t , db , database.User {})
463+ _ = dbgen .OrganizationMember (t , db , database.OrganizationMember {UserID : user .ID , OrganizationID : org .ID })
464+ tv := dbgen .TemplateVersion (t , db , database.TemplateVersion {OrganizationID : org .ID , CreatedBy : user .ID })
465+ tmpl := dbgen .Template (t , db , database.Template {OrganizationID : org .ID , ActiveVersionID : tv .ID , CreatedBy : user .ID })
466+
467+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
468+
469+ ws := dbgen .Workspace (t , db , database.WorkspaceTable {Name : "test-ws" , OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
470+ wb1 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , veryOld , 1 )
471+ wb2 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , veryOld , 2 )
472+ agent1 := mustCreateAgent (t , db , wb1 )
473+ agent2 := mustCreateAgent (t , db , wb2 )
474+ mustCreateAgentLogs (ctx , t , db , agent1 , & veryOld , "agent 1 logs" )
475+ mustCreateAgentLogs (ctx , t , db , agent2 , & veryOld , "agent 2 logs" )
476+
477+ done := awaitDoTick (ctx , t , clk )
478+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
479+ Retention : codersdk.RetentionConfig {
480+ WorkspaceAgentLogs : serpent .Duration (0 ),
481+ Global : serpent .Duration (0 ),
482+ },
483+ }, clk )
484+ defer closer .Close ()
485+ testutil .TryReceive (ctx , t , done )
486+
487+ // Both agent logs should be retained when retention is disabled.
488+ assertWorkspaceAgentLogs (ctx , t , db , agent1 .ID , "agent 1 logs" )
489+ assertWorkspaceAgentLogs (ctx , t , db , agent2 .ID , "agent 2 logs" )
490+ })
491+
492+ t .Run ("GlobalRetentionFallback" , func (t * testing.T ) {
493+ t .Parallel ()
494+
495+ ctx := testutil .Context (t , testutil .WaitShort )
496+ clk := quartz .NewMock (t )
497+ now := dbtime .Now ()
498+ retentionPeriod := 14 * 24 * time .Hour
499+ threshold := now .Add (- retentionPeriod )
500+ beforeThreshold := threshold .Add (- 24 * time .Hour )
501+ afterThreshold := threshold .Add (24 * time .Hour )
502+ clk .Set (now ).MustWait (ctx )
503+
504+ db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
505+ org := dbgen .Organization (t , db , database.Organization {})
506+ user := dbgen .User (t , db , database.User {})
507+ _ = dbgen .OrganizationMember (t , db , database.OrganizationMember {UserID : user .ID , OrganizationID : org .ID })
508+ tv := dbgen .TemplateVersion (t , db , database.TemplateVersion {OrganizationID : org .ID , CreatedBy : user .ID })
509+ tmpl := dbgen .Template (t , db , database.Template {OrganizationID : org .ID , ActiveVersionID : tv .ID , CreatedBy : user .ID })
510+
511+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
512+
513+ ws := dbgen .Workspace (t , db , database.WorkspaceTable {Name : "test-ws" , OwnerID : user .ID , OrganizationID : org .ID , TemplateID : tmpl .ID })
514+ wb1 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , beforeThreshold , 1 )
515+ wb2 := mustCreateWorkspaceBuild (t , db , org , tv , ws .ID , afterThreshold , 2 )
516+ agent1 := mustCreateAgent (t , db , wb1 )
517+ agent2 := mustCreateAgent (t , db , wb2 )
518+ mustCreateAgentLogs (ctx , t , db , agent1 , & beforeThreshold , "agent 1 logs" )
519+ mustCreateAgentLogs (ctx , t , db , agent2 , & afterThreshold , "agent 2 logs" )
520+
521+ done := awaitDoTick (ctx , t , clk )
522+ closer := dbpurge .New (ctx , logger , db , & codersdk.DeploymentValues {
523+ Retention : codersdk.RetentionConfig {
524+ WorkspaceAgentLogs : serpent .Duration (0 ),
525+ Global : serpent .Duration (retentionPeriod ),
526+ },
527+ }, clk )
528+ defer closer .Close ()
529+ testutil .TryReceive (ctx , t , done )
530+
531+ assertNoWorkspaceAgentLogs (ctx , t , db , agent1 .ID )
532+ assertWorkspaceAgentLogs (ctx , t , db , agent2 .ID , "agent 2 logs" )
533+ })
534+ }
535+
395536//nolint:paralleltest // It uses LockIDDBPurge.
396537func TestDeleteOldProvisionerDaemons (t * testing.T ) {
397538 // TODO: must refactor DeleteOldProvisionerDaemons to allow passing in cutoff
0 commit comments