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  }