Skip to content

Commit 75a0de4

Browse files
committed
feat(coderd): add retention policy configuration
Add `RetentionConfig` with server flags for configuring data retention: - `--global-retention`: default policy for all retention settings - `--audit-logs-retention`: retention for audit log entries - `--connection-logs-retention`: retention for connection logs - `--api-keys-retention`: retention for expired API keys (default 7d) Updates #20743
1 parent ffc3e81 commit 75a0de4

File tree

11 files changed

+394
-0
lines changed

11 files changed

+394
-0
lines changed

cli/testdata/coder_server_--help.golden

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,33 @@ updating, and deleting workspace resources.
696696
Number of provisioner daemons to create on start. If builds are stuck
697697
in queued state for a long time, consider increasing this.
698698

699+
RETENTION OPTIONS:
700+
Configure data retention policies for various database tables. Retention
701+
policies automatically purge old data to reduce database size and improve
702+
performance. Setting a retention duration to 0 disables automatic purging for
703+
that data type.
704+
705+
--api-keys-retention duration, $CODER_API_KEYS_RETENTION (default: 7d)
706+
How long expired API keys are retained before being deleted. Keeping
707+
expired keys allows the backend to return a more helpful error when a
708+
user tries to use an expired key. Set to 0 to disable automatic
709+
deletion of expired keys.
710+
711+
--audit-logs-retention duration, $CODER_AUDIT_LOGS_RETENTION (default: 0)
712+
How long audit log entries are retained. Set to 0 to use the global
713+
retention value, or to disable if global is also 0.
714+
715+
--connection-logs-retention duration, $CODER_CONNECTION_LOGS_RETENTION (default: 0)
716+
How long connection log entries are retained. Set to 0 to use the
717+
global retention value, or to disable if global is also 0.
718+
719+
--global-retention duration, $CODER_GLOBAL_RETENTION (default: 0)
720+
Default retention policy for audit logs, connection logs, and API
721+
keys. Individual retention settings override this value when set to a
722+
non-zero duration. Does not affect AIBridge retention. Set to 0 to
723+
disable (data is kept indefinitely unless individual settings are
724+
configured).
725+
699726
TELEMETRY OPTIONS:
700727
Telemetry is critical to our ability to improve Coder. We strip all personal
701728
information before sending data to our servers. Please only disable telemetry

cli/testdata/server-config.yaml.golden

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,27 @@ aibridge:
755755
# (token, prompt, tool use).
756756
# (default: 60d, type: duration)
757757
retention: 1440h0m0s
758+
# Configure data retention policies for various database tables. Retention
759+
# policies automatically purge old data to reduce database size and improve
760+
# performance. Setting a retention duration to 0 disables automatic purging for
761+
# that data type.
762+
retention:
763+
# Default retention policy for audit logs, connection logs, and API keys.
764+
# Individual retention settings override this value when set to a non-zero
765+
# duration. Does not affect AIBridge retention. Set to 0 to disable (data is kept
766+
# indefinitely unless individual settings are configured).
767+
# (default: 0, type: duration)
768+
global: 0s
769+
# How long audit log entries are retained. Set to 0 to use the global retention
770+
# value, or to disable if global is also 0.
771+
# (default: 0, type: duration)
772+
audit_logs: 0s
773+
# How long connection log entries are retained. Set to 0 to use the global
774+
# retention value, or to disable if global is also 0.
775+
# (default: 0, type: duration)
776+
connection_logs: 0s
777+
# How long expired API keys are retained before being deleted. Keeping expired
778+
# keys allows the backend to return a more helpful error when a user tries to use
779+
# an expired key. Set to 0 to disable automatic deletion of expired keys.
780+
# (default: 7d, type: duration)
781+
api_keys: 168h0m0s

coderd/apidoc/docs.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codersdk/deployment.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ type DeploymentValues struct {
501501
WebTerminalRenderer serpent.String `json:"web_terminal_renderer,omitempty" typescript:",notnull"`
502502
AllowWorkspaceRenames serpent.Bool `json:"allow_workspace_renames,omitempty" typescript:",notnull"`
503503
Healthcheck HealthcheckConfig `json:"healthcheck,omitempty" typescript:",notnull"`
504+
Retention RetentionConfig `json:"retention,omitempty" typescript:",notnull"`
504505
CLIUpgradeMessage serpent.String `json:"cli_upgrade_message,omitempty" typescript:",notnull"`
505506
TermsOfServiceURL serpent.String `json:"terms_of_service_url,omitempty" typescript:",notnull"`
506507
Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"`
@@ -813,6 +814,28 @@ type HealthcheckConfig struct {
813814
ThresholdDatabase serpent.Duration `json:"threshold_database" typescript:",notnull"`
814815
}
815816

817+
// RetentionConfig contains configuration for data retention policies.
818+
// These settings control how long various types of data are retained in the database
819+
// before being automatically purged. Setting a value to 0 disables retention for that
820+
// data type (data is kept indefinitely).
821+
type RetentionConfig struct {
822+
// Global is the default retention policy for audit logs, connection logs,
823+
// and API keys. Individual retention settings override this value when set
824+
// to a non-zero duration. Does not affect AIBridge retention which has its
825+
// own setting.
826+
Global serpent.Duration `json:"global" typescript:",notnull"`
827+
// AuditLogs controls how long audit log entries are retained.
828+
// Set to 0 to use the global retention value.
829+
AuditLogs serpent.Duration `json:"audit_logs" typescript:",notnull"`
830+
// ConnectionLogs controls how long connection log entries are retained.
831+
// Set to 0 to use the global retention value.
832+
ConnectionLogs serpent.Duration `json:"connection_logs" typescript:",notnull"`
833+
// APIKeys controls how long expired API keys are retained before being deleted.
834+
// Keys are only deleted if they have been expired for at least this duration.
835+
// Defaults to 7 days to preserve existing behavior.
836+
APIKeys serpent.Duration `json:"api_keys" typescript:",notnull"`
837+
}
838+
816839
type NotificationsConfig struct {
817840
// The upper limit of attempts to send a notification.
818841
MaxSendAttempts serpent.Int64 `json:"max_send_attempts" typescript:",notnull"`
@@ -1180,6 +1203,11 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
11801203
Name: "AI Bridge",
11811204
YAML: "aibridge",
11821205
}
1206+
deploymentGroupRetention = serpent.Group{
1207+
Name: "Retention",
1208+
Description: "Configure data retention policies for various database tables. Retention policies automatically purge old data to reduce database size and improve performance. Setting a retention duration to 0 disables automatic purging for that data type.",
1209+
YAML: "retention",
1210+
}
11831211
)
11841212

11851213
httpAddress := serpent.Option{
@@ -3363,6 +3391,51 @@ Write out the current server config as YAML to stdout.`,
33633391
YAML: "retention",
33643392
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
33653393
},
3394+
// Retention settings
3395+
{
3396+
Name: "Global Retention",
3397+
Description: "Default retention policy for audit logs, connection logs, and API keys. Individual retention settings override this value when set to a non-zero duration. Does not affect AIBridge retention. Set to 0 to disable (data is kept indefinitely unless individual settings are configured).",
3398+
Flag: "global-retention",
3399+
Env: "CODER_GLOBAL_RETENTION",
3400+
Value: &c.Retention.Global,
3401+
Default: "0",
3402+
Group: &deploymentGroupRetention,
3403+
YAML: "global",
3404+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3405+
},
3406+
{
3407+
Name: "Audit Logs Retention",
3408+
Description: "How long audit log entries are retained. Set to 0 to use the global retention value, or to disable if global is also 0.",
3409+
Flag: "audit-logs-retention",
3410+
Env: "CODER_AUDIT_LOGS_RETENTION",
3411+
Value: &c.Retention.AuditLogs,
3412+
Default: "0",
3413+
Group: &deploymentGroupRetention,
3414+
YAML: "audit_logs",
3415+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3416+
},
3417+
{
3418+
Name: "Connection Logs Retention",
3419+
Description: "How long connection log entries are retained. Set to 0 to use the global retention value, or to disable if global is also 0.",
3420+
Flag: "connection-logs-retention",
3421+
Env: "CODER_CONNECTION_LOGS_RETENTION",
3422+
Value: &c.Retention.ConnectionLogs,
3423+
Default: "0",
3424+
Group: &deploymentGroupRetention,
3425+
YAML: "connection_logs",
3426+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3427+
},
3428+
{
3429+
Name: "API Keys Retention",
3430+
Description: "How long expired API keys are retained before being deleted. Keeping expired keys allows the backend to return a more helpful error when a user tries to use an expired key. Set to 0 to disable automatic deletion of expired keys.",
3431+
Flag: "api-keys-retention",
3432+
Env: "CODER_API_KEYS_RETENTION",
3433+
Value: &c.Retention.APIKeys,
3434+
Default: "7d",
3435+
Group: &deploymentGroupRetention,
3436+
YAML: "api_keys",
3437+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3438+
},
33663439
{
33673440
Name: "Enable Authorization Recordings",
33683441
Description: "All api requests will have a header including all authorization calls made during the request. " +

codersdk/deployment_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,3 +689,81 @@ func TestNotificationsCanBeDisabled(t *testing.T) {
689689
})
690690
}
691691
}
692+
693+
func TestRetentionConfigParsing(t *testing.T) {
694+
t.Parallel()
695+
696+
tests := []struct {
697+
name string
698+
environment []serpent.EnvVar
699+
expectedGlobal time.Duration
700+
expectedAuditLogs time.Duration
701+
expectedConnectionLogs time.Duration
702+
expectedAPIKeys time.Duration
703+
}{
704+
{
705+
name: "Defaults",
706+
environment: []serpent.EnvVar{},
707+
expectedGlobal: 0,
708+
expectedAuditLogs: 0,
709+
expectedConnectionLogs: 0,
710+
expectedAPIKeys: 7 * 24 * time.Hour, // 7 days default
711+
},
712+
{
713+
name: "GlobalRetentionSet",
714+
environment: []serpent.EnvVar{
715+
{Name: "CODER_GLOBAL_RETENTION", Value: "90d"},
716+
},
717+
expectedGlobal: 90 * 24 * time.Hour,
718+
expectedAuditLogs: 0,
719+
expectedConnectionLogs: 0,
720+
expectedAPIKeys: 7 * 24 * time.Hour,
721+
},
722+
{
723+
name: "IndividualRetentionSet",
724+
environment: []serpent.EnvVar{
725+
{Name: "CODER_AUDIT_LOGS_RETENTION", Value: "30d"},
726+
{Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "60d"},
727+
{Name: "CODER_API_KEYS_RETENTION", Value: "14d"},
728+
},
729+
expectedGlobal: 0,
730+
expectedAuditLogs: 30 * 24 * time.Hour,
731+
expectedConnectionLogs: 60 * 24 * time.Hour,
732+
expectedAPIKeys: 14 * 24 * time.Hour,
733+
},
734+
{
735+
name: "AllRetentionSet",
736+
environment: []serpent.EnvVar{
737+
{Name: "CODER_GLOBAL_RETENTION", Value: "90d"},
738+
{Name: "CODER_AUDIT_LOGS_RETENTION", Value: "365d"},
739+
{Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "30d"},
740+
{Name: "CODER_API_KEYS_RETENTION", Value: "0"},
741+
},
742+
expectedGlobal: 90 * 24 * time.Hour,
743+
expectedAuditLogs: 365 * 24 * time.Hour,
744+
expectedConnectionLogs: 30 * 24 * time.Hour,
745+
expectedAPIKeys: 0, // Explicitly disabled
746+
},
747+
}
748+
749+
for _, tt := range tests {
750+
tt := tt
751+
t.Run(tt.name, func(t *testing.T) {
752+
t.Parallel()
753+
754+
dv := codersdk.DeploymentValues{}
755+
opts := dv.Options()
756+
757+
err := opts.SetDefaults()
758+
require.NoError(t, err)
759+
760+
err = opts.ParseEnv(tt.environment)
761+
require.NoError(t, err)
762+
763+
assert.Equal(t, tt.expectedGlobal, dv.Retention.Global.Value(), "global retention mismatch")
764+
assert.Equal(t, tt.expectedAuditLogs, dv.Retention.AuditLogs.Value(), "audit logs retention mismatch")
765+
assert.Equal(t, tt.expectedConnectionLogs, dv.Retention.ConnectionLogs.Value(), "connection logs retention mismatch")
766+
assert.Equal(t, tt.expectedAPIKeys, dv.Retention.APIKeys.Value(), "api keys retention mismatch")
767+
})
768+
}
769+
}

docs/reference/api/general.md

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)