github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/cloud/aws/cache/cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/aquasecurity/defsec/pkg/state"
    13  )
    14  
    15  type Cache struct {
    16  	path      string
    17  	accountID string
    18  	region    string
    19  	maxAge    time.Duration
    20  }
    21  
    22  const SchemaVersion = 2
    23  
    24  type CacheData struct {
    25  	SchemaVersion int                        `json:"schema_version"`
    26  	State         *state.State               `json:"state"`
    27  	Services      map[string]ServiceMetadata `json:"service_metadata"`
    28  	Updated       time.Time                  `json:"updated"`
    29  }
    30  
    31  type ServiceMetadata struct {
    32  	Name    string    `json:"name"`
    33  	Updated time.Time `json:"updated"`
    34  }
    35  
    36  var ErrCacheNotFound = fmt.Errorf("cache record not found")
    37  var ErrCacheIncompatible = fmt.Errorf("cache record used incomatible schema")
    38  var ErrCacheExpired = fmt.Errorf("cache record expired")
    39  
    40  func New(cacheDir string, maxCacheAge time.Duration, accountID, region string) *Cache {
    41  	return &Cache{
    42  		path:      path.Join(cacheDir, "cloud", "aws", accountID, strings.ToLower(region), "data.json"),
    43  		accountID: accountID,
    44  		region:    region,
    45  		maxAge:    maxCacheAge,
    46  	}
    47  }
    48  
    49  func (c *Cache) load() (*CacheData, error) {
    50  
    51  	m, err := os.Open(c.path)
    52  	if err != nil {
    53  		return nil, ErrCacheNotFound
    54  	}
    55  	defer func() { _ = m.Close() }()
    56  
    57  	var data CacheData
    58  	if err := json.NewDecoder(m).Decode(&data); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	if data.SchemaVersion != SchemaVersion {
    63  		return nil, ErrCacheIncompatible
    64  	}
    65  
    66  	if time.Since(data.Updated) > c.maxAge {
    67  		return nil, ErrCacheExpired
    68  	}
    69  
    70  	return &data, nil
    71  }
    72  
    73  func (c *Cache) ListServices(required []string) (included, missing []string) {
    74  
    75  	data, err := c.load()
    76  	if err != nil {
    77  		return nil, required
    78  	}
    79  
    80  	for _, service := range required {
    81  		metadata, ok := data.Services[service]
    82  		if !ok {
    83  			missing = append(missing, service)
    84  			continue
    85  		}
    86  		if time.Since(metadata.Updated) > c.maxAge {
    87  			missing = append(missing, service)
    88  			continue
    89  		}
    90  		included = append(included, service)
    91  	}
    92  
    93  	return included, missing
    94  }
    95  
    96  func (c *Cache) LoadState() (*state.State, error) {
    97  	data, err := c.load()
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	return data.State, nil
   102  }
   103  
   104  func (c *Cache) AddServices(s *state.State, includedServices []string) error {
   105  	data := &CacheData{
   106  		SchemaVersion: SchemaVersion,
   107  		State:         s,
   108  		Services:      make(map[string]ServiceMetadata),
   109  		Updated:       time.Now(),
   110  	}
   111  
   112  	if previous, err := c.load(); err == nil {
   113  		data.Services = previous.Services
   114  	}
   115  
   116  	for _, service := range includedServices {
   117  		data.Services[service] = ServiceMetadata{
   118  			Name:    service,
   119  			Updated: time.Now(),
   120  		}
   121  	}
   122  
   123  	if err := os.MkdirAll(filepath.Dir(c.path), 0700); err != nil {
   124  		return err
   125  	}
   126  	f, err := os.Create(c.path)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer func() { _ = f.Close() }()
   131  	return json.NewEncoder(f).Encode(data)
   132  }