package config import ( "encoding" "strconv" "strings" "time" "github.com/docker/go-units" "github.com/moby/buildkit/util/disk" "github.com/pkg/errors" ) type Duration struct { time.Duration } func (d *Duration) UnmarshalText(textb []byte) error { text := stripQuotes(string(textb)) if len(text) == 0 { return nil } if duration, err := time.ParseDuration(text); err == nil { d.Duration = duration return nil } if i, err := strconv.ParseInt(text, 10, 64); err == nil { d.Duration = time.Duration(i) * time.Second return nil } return errors.Errorf("invalid duration %s", text) } var _ encoding.TextUnmarshaler = &Duration{} type DiskSpace struct { Bytes int64 Percentage int64 } var _ encoding.TextUnmarshaler = &DiskSpace{} func (d *DiskSpace) UnmarshalText(textb []byte) error { text := stripQuotes(string(textb)) if len(text) == 0 { return nil } if text2 := strings.TrimSuffix(text, "%"); len(text2) < len(text) { i, err := strconv.ParseInt(text2, 10, 64) if err != nil { return err } d.Percentage = i return nil } if i, err := units.RAMInBytes(text); err == nil { d.Bytes = i return nil } return errors.Errorf("invalid disk space %s", text) } const defaultCap int64 = 2e9 // 2GB func DefaultGCPolicy(cfg GCConfig, dstat disk.DiskStat) []GCPolicy { if cfg.IsUnset() { cfg.GCReservedSpace = cfg.GCKeepStorage } if cfg.IsUnset() { cfg = DetectDefaultGCCap(dstat) } return []GCPolicy{ // if build cache uses more than 512MB delete the most easily reproducible data after it has not been used for 2 days { Filters: []string{"type==source.local,type==exec.cachemount,type==source.git.checkout"}, KeepDuration: Duration{Duration: time.Duration(48) * time.Hour}, // 48h MaxUsedSpace: DiskSpace{Bytes: 512 * 1e6}, // 512MB }, // remove any data not used for 60 days { KeepDuration: Duration{Duration: time.Duration(60) * 24 * time.Hour}, // 60d MinFreeSpace: cfg.GCMinFreeSpace, ReservedSpace: cfg.GCReservedSpace, MaxUsedSpace: cfg.GCMaxUsedSpace, }, // keep the unshared build cache under cap { MinFreeSpace: cfg.GCMinFreeSpace, ReservedSpace: cfg.GCReservedSpace, MaxUsedSpace: cfg.GCMaxUsedSpace, }, // if previous policies were insufficient start deleting internal data to keep build cache under cap { All: true, MinFreeSpace: cfg.GCMinFreeSpace, ReservedSpace: cfg.GCReservedSpace, MaxUsedSpace: cfg.GCMaxUsedSpace, }, } } func stripQuotes(s string) string { if len(s) == 0 { return s } if s[0] == '"' && s[len(s)-1] == '"' { return s[1 : len(s)-1] } return s } func DetectDefaultGCCap(dstat disk.DiskStat) GCConfig { reserve := DiskSpace{Percentage: DiskSpaceReservePercentage} if reserve.AsBytes(dstat) > DiskSpaceReserveBytes { reserve = DiskSpace{Bytes: DiskSpaceReserveBytes} } max := DiskSpace{Percentage: DiskSpaceMaxPercentage} if max.AsBytes(dstat) > DiskSpaceMaxBytes { max = DiskSpace{Bytes: DiskSpaceMaxBytes} } return GCConfig{ GCReservedSpace: reserve, GCMinFreeSpace: DiskSpace{Percentage: DiskSpaceFreePercentage}, GCMaxUsedSpace: max, } } func (d DiskSpace) AsBytes(dstat disk.DiskStat) int64 { if d.Bytes != 0 { return d.Bytes } if d.Percentage == 0 { return 0 } if dstat.Total == 0 { return defaultCap } avail := dstat.Total * d.Percentage / 100 rounded := (avail/(1<<30) + 1) * 1e9 // round up return rounded } func (cfg *GCConfig) IsUnset() bool { return cfg.GCReservedSpace == DiskSpace{} && cfg.GCMaxUsedSpace == DiskSpace{} && cfg.GCMinFreeSpace == DiskSpace{} }