Add prune retention planning
This commit is contained in:
@@ -57,6 +57,7 @@ type Destination struct {
|
||||
Links *Links `yaml:"links"`
|
||||
State StatePolicy `yaml:"state"`
|
||||
Reconciliation ReconciliationPolicy `yaml:"reconciliation"`
|
||||
Retention RetentionPolicy `yaml:"retention"`
|
||||
Transfer TransferPolicy `yaml:"transfer"`
|
||||
}
|
||||
|
||||
@@ -128,6 +129,16 @@ type StatePolicy struct {
|
||||
Mode string `yaml:"mode"`
|
||||
}
|
||||
|
||||
type RetentionPolicy struct {
|
||||
Prune PrunePolicy `yaml:"prune"`
|
||||
}
|
||||
|
||||
type PrunePolicy struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
OlderThan *Duration `yaml:"older_than"`
|
||||
KeepLatest *int `yaml:"keep_latest"`
|
||||
}
|
||||
|
||||
type TransferPolicy struct {
|
||||
OnDestinationSame string `yaml:"on_destination_same"`
|
||||
OnDestinationOlder string `yaml:"on_destination_older"`
|
||||
|
||||
@@ -39,6 +39,9 @@ pipelines:
|
||||
if got, want := destination.State.Mode, StateModeSingleOwner; got != want {
|
||||
t.Fatalf("state mode default = %q, want %q", got, want)
|
||||
}
|
||||
if destination.Retention.Prune.Enabled {
|
||||
t.Fatal("retention.prune.enabled default = true, want false")
|
||||
}
|
||||
if cfg.Secrets.Directory != "" {
|
||||
t.Fatalf("secrets.directory = %q, want empty", cfg.Secrets.Directory)
|
||||
}
|
||||
@@ -228,6 +231,36 @@ pipelines:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFileAcceptsRetentionPruneConfig(t *testing.T) {
|
||||
cfg := loadConfig(t, `
|
||||
pipelines:
|
||||
- id: reports
|
||||
source:
|
||||
backend: local
|
||||
path: /source
|
||||
destinations:
|
||||
- id: archive
|
||||
backend: local
|
||||
path: /archive
|
||||
retention:
|
||||
prune:
|
||||
enabled: true
|
||||
older_than: 168h
|
||||
keep_latest: 3
|
||||
`)
|
||||
|
||||
prune := cfg.Pipelines[0].Destinations[0].Retention.Prune
|
||||
if !prune.Enabled {
|
||||
t.Fatal("retention.prune.enabled = false, want true")
|
||||
}
|
||||
if prune.OlderThan == nil || prune.OlderThan.String() != "168h0m0s" {
|
||||
t.Fatalf("retention.prune.older_than = %v, want 168h", prune.OlderThan)
|
||||
}
|
||||
if prune.KeepLatest == nil || *prune.KeepLatest != 3 {
|
||||
t.Fatalf("retention.prune.keep_latest = %v, want 3", prune.KeepLatest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadFileAcceptsFixedPathMapping(t *testing.T) {
|
||||
cfg := loadConfig(t, `
|
||||
pipelines:
|
||||
@@ -705,6 +738,7 @@ pipelines: [{id: reports, source: {backend: http_upload, max_upload_size: 20XB},
|
||||
pipelines: [{id: reports, source: {backend: http_upload, max_upload_size: 0B}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
|
||||
"server duration": `server: {http: {retention: forever}}`,
|
||||
"zero server duration": `server: {http: {retention: 0s}}`,
|
||||
"prune duration": `pipelines: [{id: reports, source: {backend: local, path: /source}, destinations: [{id: archive, backend: local, path: /archive, retention: {prune: {enabled: true, older_than: forever}}}]}]`,
|
||||
"missing upload tokens": `pipelines: [{id: reports, source: {backend: http_upload}, destinations: [{id: archive, backend: local, path: /archive}]}]`,
|
||||
"destination http upload": `pipelines: [{id: reports, source: {backend: local, path: /source}, destinations: [{id: ingest, backend: http_upload}]}]`,
|
||||
"literal token": `upload_tokens: [{id: reporter, token: secret, token_env: UPLOAD_TOKEN, allow_pipelines: [reports]}]
|
||||
|
||||
@@ -74,6 +74,7 @@ func Validate(cfg Config) error {
|
||||
errs = validateLinks(errs, destinationContext+".links", destination.Links)
|
||||
errs = validateStatePolicy(errs, destinationContext+".state", destination.State)
|
||||
errs = validateReconciliationPolicy(errs, destinationContext+".reconciliation", destination.Reconciliation)
|
||||
errs = validateRetentionPolicy(errs, destinationContext+".retention", destination.Retention)
|
||||
errs = validateTransferPolicy(errs, destinationContext+".transfer", destination.Transfer)
|
||||
}
|
||||
}
|
||||
@@ -323,6 +324,23 @@ func validateReconciliationPolicy(errs ValidationErrors, context string, policy
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateRetentionPolicy(errs ValidationErrors, context string, policy RetentionPolicy) ValidationErrors {
|
||||
prune := policy.Prune
|
||||
if !prune.Enabled {
|
||||
return errs
|
||||
}
|
||||
if prune.OlderThan == nil && prune.KeepLatest == nil {
|
||||
errs = append(errs, context+".prune must set older_than or keep_latest when enabled is true")
|
||||
}
|
||||
if prune.OlderThan != nil && *prune.OlderThan <= 0 {
|
||||
errs = append(errs, context+".prune.older_than must be greater than zero")
|
||||
}
|
||||
if prune.KeepLatest != nil && *prune.KeepLatest < 0 {
|
||||
errs = append(errs, context+".prune.keep_latest must be zero or greater")
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func validateStatePolicy(errs ValidationErrors, context string, policy StatePolicy) ValidationErrors {
|
||||
if policy.Mode != StateModeSingleOwner && policy.Mode != StateModeSharedRoot {
|
||||
errs = append(errs, context+".mode must be "+StateModeSingleOwner+" or "+StateModeSharedRoot)
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestValidatePublishTransformPolicy(t *testing.T) {
|
||||
@@ -175,6 +176,50 @@ func TestValidateStatePolicy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRetentionPolicy(t *testing.T) {
|
||||
olderThan := Duration(24 * time.Hour)
|
||||
zeroDuration := Duration(0)
|
||||
keepZero := 0
|
||||
keepThree := 3
|
||||
keepNegative := -1
|
||||
tests := []struct {
|
||||
name string
|
||||
retention RetentionPolicy
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "disabled"},
|
||||
{name: "older than", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &olderThan}}},
|
||||
{name: "keep zero", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepZero}}},
|
||||
{name: "keep latest", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepThree}}},
|
||||
{name: "combined", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &olderThan, KeepLatest: &keepThree}}},
|
||||
{name: "missing policy", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true}}, wantErr: true},
|
||||
{name: "zero older than", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, OlderThan: &zeroDuration}}, wantErr: true},
|
||||
{name: "negative keep latest", retention: RetentionPolicy{Prune: PrunePolicy{Enabled: true, KeepLatest: &keepNegative}}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := Config{Pipelines: []Pipeline{{
|
||||
ID: "reports",
|
||||
Source: Backend{Backend: BackendLocal, Path: "/source"},
|
||||
Destinations: []Destination{{
|
||||
ID: "archive",
|
||||
Backend: BackendLocal,
|
||||
Path: "/destination",
|
||||
Retention: tt.retention,
|
||||
}},
|
||||
}}}
|
||||
ApplyDefaults(&cfg)
|
||||
err := Validate(cfg)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Fatal("Validate() error = nil, want error")
|
||||
}
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Fatalf("Validate() error = %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLinks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user