Skip to content

Commit

Permalink
fix: separate golang license caches from mod dir (#2852)
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Zantow <[email protected]>
  • Loading branch information
kzantow authored Jun 12, 2024
1 parent dd723bb commit ca0cc52
Show file tree
Hide file tree
Showing 31 changed files with 1,484 additions and 137 deletions.
2 changes: 2 additions & 0 deletions cmd/syft/internal/commands/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type attestOptions struct {
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
Attest options.Attest `yaml:"attest" mapstructure:"attest"`
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
}

func Attest(app clio.Application) *cobra.Command {
Expand Down Expand Up @@ -77,6 +78,7 @@ func defaultAttestOptions() attestOptions {
Output: defaultAttestOutputOptions(),
UpdateCheck: options.DefaultUpdateCheck(),
Catalog: options.DefaultCatalog(),
Cache: options.DefaultCache(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/syft/internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ type scanOptions struct {
options.Output `yaml:",inline" mapstructure:",squash"`
options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
options.Catalog `yaml:",inline" mapstructure:",squash"`
Cache options.Cache `json:"-" yaml:"cache" mapstructure:"cache"`
}

func defaultScanOptions() *scanOptions {
return &scanOptions{
Output: options.DefaultOutput(),
UpdateCheck: options.DefaultUpdateCheck(),
Catalog: options.DefaultCatalog(),
Cache: options.DefaultCache(),
}
}

Expand Down
122 changes: 122 additions & 0 deletions cmd/syft/internal/options/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package options

import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"

"github.com/adrg/xdg"
"github.com/mitchellh/go-homedir"

"github.com/anchore/clio"
"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")
descriptions.Add(&c.TTL, "time to live for cached data")
}

func (c *Cache) PostLoad() error {
if c.Dir != "" {
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()
}
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
}

var whitespace = regexp.MustCompile(`\s+`)

func parseDuration(duration string) (time.Duration, error) {
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
remain, err = time.ParseDuration(parts[1])
} else {
remain, err = time.ParseDuration(duration)
}
return days + remain, err
}
184 changes: 184 additions & 0 deletions cmd/syft/internal/options/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package options

import (
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/adrg/xdg"
"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/require"
)

func Test_defaultDir(t *testing.T) {
tmpDir := filepath.Join(t.TempDir(), "cache-temp")
xdgCacheDir := filepath.Join(tmpDir, "fake-xdg-cache")
homeDir := filepath.Join(tmpDir, "fake-home")

tests := []struct {
name string
env map[string]string
expected string
}{
{
name: "no-xdg",
env: map[string]string{
"HOME": homeDir,
},
expected: homeDir,
},
{
name: "xdg-cache",
env: map[string]string{
"XDG_CACHE_HOME": xdgCacheDir,
},
expected: xdgCacheDir,
},
}

// capture all the initial environment variables to reset them before we reset library caches
env := map[string]string{
"HOME": "",
"XDG_DATA_HOME": "",
"XDG_DATA_DIRS": "",
"XDG_CONFIG_HOME": "",
"XDG_CONFIG_DIRS": "",
"XDG_STATE_HOME": "",
"XDG_CACHE_HOME": "",
"XDG_RUNTIME_DIR": "",
}
for k := range env {
env[k] = os.Getenv(k)
}

unsetEnv := func(t *testing.T) {
for k := range env {
t.Setenv(k, "")
}
}

resetEnv := func() {
for k, v := range env {
if v == "" {
_ = os.Unsetenv(k)
} else {
_ = os.Setenv(k, v)
}
}
homedir.Reset()
xdg.Reload()
}

t.Cleanup(resetEnv)

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
defer resetEnv()

unsetEnv(t)
for k, v := range test.env {
t.Setenv(k, v)
}
homedir.Reset()
xdg.Reload()

got := defaultDir()

require.True(t, strings.HasPrefix(got, test.expected))
})
}
}

func Test_parseDuration(t *testing.T) {
tests := []struct {
duration string
expect time.Duration
err require.ErrorAssertionFunc
}{
{
duration: "1d",
expect: 24 * time.Hour,
},
{
duration: "7d",
expect: 7 * 24 * time.Hour,
},
{
duration: "365D",
expect: 365 * 24 * time.Hour,
},
{
duration: "7d1h1m1s",
expect: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
},
{
duration: "7d 1h 1m 1s",
expect: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
},
{
duration: "2h",
expect: 2 * time.Hour,
},
{
duration: "2h5m",
expect: 2*time.Hour + 5*time.Minute,
},
{
duration: "2h 5m",
expect: 2*time.Hour + 5*time.Minute,
},
{
duration: "d24h",
err: require.Error,
},
}

for _, test := range tests {
t.Run(test.duration, func(t *testing.T) {
got, err := parseDuration(test.duration)
if test.err != nil {
test.err(t, err)
return
}
require.Equal(t, test.expect, got)
})
}
}

func Test_durationToString(t *testing.T) {
tests := []struct {
duration time.Duration
expect string
err require.ErrorAssertionFunc
}{
{
expect: "1d",
duration: 24 * time.Hour,
},
{
expect: "7d",
duration: 7 * 24 * time.Hour,
},
{
expect: "7d1h1m1s",
duration: 7*24*time.Hour + time.Hour + time.Minute + time.Second,
},
{
expect: "2h0m0s",
duration: 2 * time.Hour,
},
{
expect: "2h5m0s",
duration: 2*time.Hour + 5*time.Minute,
},
}

for _, test := range tests {
t.Run(test.expect, func(t *testing.T) {
got := durationToString(test.duration)
require.Equal(t, test.expect, got)
})
}
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ require (

require google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect

require github.com/magiconair/properties v1.8.7
require (
github.com/adrg/xdg v0.4.0
github.com/magiconair/properties v1.8.7
)

require (
dario.cat/mergo v1.0.0 // indirect
Expand All @@ -98,7 +101,6 @@ require (
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/adrg/xdg v0.4.0 // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect
Expand Down
Loading

0 comments on commit ca0cc52

Please sign in to comment.