github.com/anchore/syft@v1.38.2/cmd/syft/internal/options/cache.go (about) 1 package options 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "regexp" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/adrg/xdg" 13 14 "github.com/anchore/clio" 15 "github.com/anchore/go-homedir" 16 "github.com/anchore/syft/internal/cache" 17 "github.com/anchore/syft/internal/log" 18 ) 19 20 // Cache provides configuration for the Syft caching behavior 21 type Cache struct { 22 Dir string `yaml:"dir" mapstructure:"dir"` 23 TTL string `yaml:"ttl" mapstructure:"ttl"` 24 } 25 26 func (c *Cache) DescribeFields(descriptions clio.FieldDescriptionSet) { 27 descriptions.Add(&c.Dir, "root directory to cache any downloaded content; empty string will use an in-memory cache") 28 descriptions.Add(&c.TTL, "time to live for cached data; setting this to 0 will disable caching entirely") 29 } 30 31 func (c *Cache) PostLoad() error { 32 ttl, err := parseDuration(c.TTL) 33 if err != nil { 34 log.Warnf("unable to parse duration '%v', using default (%s) due to: %v", c.TTL, durationToString(defaultTTL()), err) 35 ttl = defaultTTL() 36 } 37 // if TTL is <= 0, disable caching entirely 38 if ttl <= 0 { 39 cache.SetManager(nil) 40 return nil 41 } 42 // if dir == "" but we have a TTL, use an in-memory cache 43 if c.Dir == "" { 44 cache.SetManager(cache.NewInMemory(ttl)) 45 return nil 46 } 47 dir, err := homedir.Expand(c.Dir) 48 if err != nil { 49 log.Warnf("unable to expand cache directory %s: %v", c.Dir, err) 50 cache.SetManager(cache.NewInMemory(ttl)) 51 } else { 52 m, err := cache.NewFromDir(dir, ttl) 53 if err != nil { 54 log.Warnf("unable to get filesystem cache at %s: %v", c.Dir, err) 55 cache.SetManager(cache.NewInMemory(ttl)) 56 } else { 57 cache.SetManager(m) 58 } 59 } 60 return nil 61 } 62 63 var _ interface { 64 clio.PostLoader 65 clio.FieldDescriber 66 } = (*Cache)(nil) 67 68 func DefaultCache() Cache { 69 return Cache{ 70 Dir: defaultDir(), 71 TTL: durationToString(defaultTTL()), 72 } 73 } 74 75 func defaultTTL() time.Duration { 76 return 7 * 24 * time.Hour 77 } 78 79 func defaultDir() string { 80 var err error 81 cacheRoot := xdg.CacheHome 82 if cacheRoot == "" { 83 cacheRoot, err = homedir.Dir() 84 if err != nil { 85 cacheRoot = os.TempDir() 86 log.Debugf("unable to get stable cache directory due to: %v, defaulting cache to temp dir: %s", err, cacheRoot) 87 } else { 88 cacheRoot = filepath.Join(cacheRoot, ".cache") 89 } 90 } 91 92 return filepath.Join(cacheRoot, "syft") 93 } 94 95 func durationToString(duration time.Duration) string { 96 days := int64(duration / (24 * time.Hour)) 97 remain := duration % (24 * time.Hour) 98 out := "" 99 if days > 0 { 100 out = fmt.Sprintf("%vd", days) 101 } 102 if remain != 0 { 103 out += remain.String() 104 } 105 if out == "" { 106 return "0" 107 } 108 return out 109 } 110 111 func parseDuration(duration string) (time.Duration, error) { 112 whitespace := regexp.MustCompile(`\s+`) 113 duration = strings.ToLower(whitespace.ReplaceAllString(duration, "")) 114 parts := strings.SplitN(duration, "d", 2) 115 var days time.Duration 116 var remain time.Duration 117 var err error 118 if len(parts) > 1 { 119 numDays, daysErr := strconv.Atoi(parts[0]) 120 if daysErr != nil { 121 return 0, daysErr 122 } 123 days = time.Duration(numDays) * 24 * time.Hour 124 if len(parts[1]) > 0 { 125 remain, err = time.ParseDuration(parts[1]) 126 } 127 } else { 128 remain, err = time.ParseDuration(duration) 129 } 130 return days + remain, err 131 }