package options import ( "fmt" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/adrg/xdg" "github.com/anchore/clio" "github.com/anchore/go-homedir" "github.com/anchore/syft/internal/cache" "github.com/anchore/syft/internal/log" ) // Cache provides configuration for the Syft caching behavior type Cache struct { Dir string `yaml:"dir" mapstructure:"dir"` TTL string `yaml:"ttl" mapstructure:"ttl"` } func (c *Cache) DescribeFields(descriptions clio.FieldDescriptionSet) { descriptions.Add(&c.Dir, "root directory to cache any downloaded content; empty string will use an in-memory cache") descriptions.Add(&c.TTL, "time to live for cached data; setting this to 0 will disable caching entirely") } func (c *Cache) PostLoad() error { ttl, err := parseDuration(c.TTL) if err != nil { log.Warnf("unable to parse duration '%v', using default (%s) due to: %v", c.TTL, durationToString(defaultTTL()), err) ttl = defaultTTL() } // if TTL is <= 0, disable caching entirely if ttl <= 0 { cache.SetManager(nil) return nil } // if dir == "" but we have a TTL, use an in-memory cache if c.Dir == "" { cache.SetManager(cache.NewInMemory(ttl)) return nil } dir, err := homedir.Expand(c.Dir) if err != nil { log.Warnf("unable to expand cache directory %s: %v", c.Dir, err) cache.SetManager(cache.NewInMemory(ttl)) } else { m, err := cache.NewFromDir(dir, ttl) if err != nil { log.Warnf("unable to get filesystem cache at %s: %v", c.Dir, err) cache.SetManager(cache.NewInMemory(ttl)) } else { cache.SetManager(m) } } return nil } var _ interface { clio.PostLoader clio.FieldDescriber } = (*Cache)(nil) func DefaultCache() Cache { return Cache{ Dir: defaultDir(), TTL: durationToString(defaultTTL()), } } func defaultTTL() time.Duration { return 7 * 24 * time.Hour } func defaultDir() string { var err error cacheRoot := xdg.CacheHome if cacheRoot == "" { cacheRoot, err = homedir.Dir() if err != nil { cacheRoot = os.TempDir() log.Debugf("unable to get stable cache directory due to: %v, defaulting cache to temp dir: %s", err, cacheRoot) } else { cacheRoot = filepath.Join(cacheRoot, ".cache") } } return filepath.Join(cacheRoot, "syft") } func durationToString(duration time.Duration) string { days := int64(duration / (24 * time.Hour)) remain := duration % (24 * time.Hour) out := "" if days > 0 { out = fmt.Sprintf("%vd", days) } if remain != 0 { out += remain.String() } if out == "" { return "0" } return out } func parseDuration(duration string) (time.Duration, error) { whitespace := regexp.MustCompile(`\s+`) duration = strings.ToLower(whitespace.ReplaceAllString(duration, "")) parts := strings.SplitN(duration, "d", 2) var days time.Duration var remain time.Duration var err error if len(parts) > 1 { numDays, daysErr := strconv.Atoi(parts[0]) if daysErr != nil { return 0, daysErr } days = time.Duration(numDays) * 24 * time.Hour if len(parts[1]) > 0 { remain, err = time.ParseDuration(parts[1]) } } else { remain, err = time.ParseDuration(duration) } return days + remain, err }