mirror of
https://github.com/anchore/syft.git
synced 2026-11-03 16:06:36 +01:00
chore: simplify and cleanup
Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
This commit is contained in:
@@ -1,47 +1,46 @@
|
||||
package archive
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/tmpdir"
|
||||
)
|
||||
|
||||
// Seams the package itself does not need, kept here so no production file carries a function only
|
||||
// tests call.
|
||||
|
||||
// WorkDirAt returns a WorkDir over a directory that already exists. Production always creates its own
|
||||
// (NewWorkDir); a test lends one it can inspect afterwards, so Remove leaves it alone.
|
||||
// WorkDirAt returns a WorkDir over a directory that already exists, so a test can inspect it
|
||||
// afterwards; Remove leaves it alone.
|
||||
func WorkDirAt(dir string) *WorkDir {
|
||||
return &WorkDir{path: dir, done: true}
|
||||
return &WorkDir{created: true, path: dir}
|
||||
}
|
||||
|
||||
// heldInMemory reports the entry content this store is still holding in memory. Production never asks:
|
||||
// the limiter is what bounds it, and the store charges as it goes rather than tracking a total.
|
||||
func (w *WorkDir) wasCreated() bool {
|
||||
return w != nil && w.remove != nil
|
||||
}
|
||||
|
||||
// heldInMemory reports the entry content the store is still holding in memory.
|
||||
func (s *EntryStore) heldInMemory() int64 {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
var total int64
|
||||
for _, entry := range s.entries {
|
||||
if entry.ref.mem != nil {
|
||||
total += entry.ref.length
|
||||
}
|
||||
total += int64(len(entry.mem))
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// created reports whether anything ever asked for the directory, and so whether there is anything to
|
||||
// remove.
|
||||
func (w *WorkDir) created() bool {
|
||||
if w == nil {
|
||||
return false
|
||||
}
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
return w.remove != nil
|
||||
}
|
||||
|
||||
// held reports what this charge is currently holding, in memory and on disk.
|
||||
func (c *Charge) held() (memory, disk int64) {
|
||||
if c == nil || c.limiter == nil {
|
||||
if c == nil {
|
||||
return 0, 0
|
||||
}
|
||||
c.limiter.mu.Lock()
|
||||
@@ -49,11 +48,209 @@ func (c *Charge) held() (memory, disk int64) {
|
||||
return c.memory, c.disk
|
||||
}
|
||||
|
||||
// openFileContent opens an archive already on disk as Content. The caller closes it.
|
||||
func openFileContent(path string) (Content, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return Content{}, err
|
||||
// unseekable hides a reader's Seek and ReadAt, forcing content through the held path.
|
||||
type unseekable struct{ io.Reader }
|
||||
|
||||
// testEntry is one entry of a fixture archive: a directory when dir is set, a symlink when link is
|
||||
// set, otherwise a regular file.
|
||||
type testEntry struct {
|
||||
name string
|
||||
body string
|
||||
link string
|
||||
dir bool
|
||||
mode fs.FileMode
|
||||
}
|
||||
|
||||
func fileEntries(files map[string]string) []testEntry {
|
||||
names := make([]string, 0, len(files))
|
||||
for name := range files {
|
||||
names = append(names, name)
|
||||
}
|
||||
return Content{Name: filepath.Base(path), Reader: f, closer: f}, nil
|
||||
// limits bite as the walk proceeds, so entry order decides what lands before a truncation
|
||||
sort.Strings(names)
|
||||
entries := make([]testEntry, 0, len(names))
|
||||
for _, name := range names {
|
||||
entries = append(entries, testEntry{name: name, body: files[name]})
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func zipBytes(t testing.TB, files map[string]string) []byte {
|
||||
return zipFromEntries(t, fileEntries(files))
|
||||
}
|
||||
|
||||
func zipFromEntries(t testing.TB, entries []testEntry) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
w := zip.NewWriter(&buf)
|
||||
for _, e := range entries {
|
||||
hdr := &zip.FileHeader{Name: e.name}
|
||||
body := e.body
|
||||
switch {
|
||||
case e.dir:
|
||||
hdr.SetMode(fs.ModeDir | e.modeOr(0o755))
|
||||
case e.link != "":
|
||||
hdr.SetMode(fs.ModeSymlink | 0o777)
|
||||
body = e.link
|
||||
default:
|
||||
hdr.SetMode(e.modeOr(0o644))
|
||||
}
|
||||
fw, err := w.CreateHeader(hdr)
|
||||
require.NoError(t, err)
|
||||
_, err = fw.Write([]byte(body))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, w.Close())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func tarGzBytes(t testing.TB, files map[string]string) []byte {
|
||||
return tarGzFromEntries(t, fileEntries(files))
|
||||
}
|
||||
|
||||
func tarGzFromEntries(t testing.TB, entries []testEntry) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
tw := tar.NewWriter(gw)
|
||||
for _, e := range entries {
|
||||
hdr := &tar.Header{Name: e.name, Mode: int64(e.modeOr(0o644)), Size: int64(len(e.body))}
|
||||
switch {
|
||||
case e.dir:
|
||||
hdr.Typeflag, hdr.Mode, hdr.Size = tar.TypeDir, int64(e.modeOr(0o755)), 0
|
||||
case e.link != "":
|
||||
hdr.Typeflag, hdr.Linkname, hdr.Mode, hdr.Size = tar.TypeSymlink, e.link, 0o777, 0
|
||||
}
|
||||
require.NoError(t, tw.WriteHeader(hdr))
|
||||
_, err := tw.Write([]byte(e.body))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.NoError(t, tw.Close())
|
||||
require.NoError(t, gw.Close())
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (e testEntry) modeOr(fallback fs.FileMode) fs.FileMode {
|
||||
if e.mode != 0 {
|
||||
return e.mode
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func regularHeader(name string, size int64) tar.Header {
|
||||
return tar.Header{Name: name, Size: size, Mode: 0o600, Typeflag: tar.TypeReg}
|
||||
}
|
||||
|
||||
// storeFor makes a store in the test's own temp space with the given charge.
|
||||
func storeFor(t testing.TB, charge *Charge) *EntryStore {
|
||||
t.Helper()
|
||||
s := NewEntryStore("test-archive", WorkDirAt(t.TempDir()), charge)
|
||||
t.Cleanup(func() { _ = s.Close() })
|
||||
return s
|
||||
}
|
||||
|
||||
// storeDir is the directory a store spills into.
|
||||
func storeDir(t testing.TB, s *EntryStore) string {
|
||||
t.Helper()
|
||||
dir, err := s.workDir.Path()
|
||||
require.NoError(t, err)
|
||||
return dir
|
||||
}
|
||||
|
||||
func readEntry(t testing.TB, r ReaderAtSeeker) string {
|
||||
t.Helper()
|
||||
_, err := r.Seek(0, io.SeekStart)
|
||||
require.NoError(t, err)
|
||||
b, err := io.ReadAll(r)
|
||||
require.NoError(t, err)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// storedEntry is one entry read back out of a store.
|
||||
type storedEntry struct {
|
||||
body string
|
||||
linkTarget string
|
||||
mode fs.FileMode
|
||||
typeflag byte
|
||||
}
|
||||
|
||||
func readStore(t testing.TB, s *EntryStore) map[string]storedEntry {
|
||||
t.Helper()
|
||||
out := map[string]storedEntry{}
|
||||
for _, entry := range s.Entries() {
|
||||
out[entry.Header.Name] = storedEntry{
|
||||
body: readEntry(t, s.Open(entry)),
|
||||
linkTarget: entry.Header.Linkname,
|
||||
mode: entry.Header.FileInfo().Mode(),
|
||||
typeflag: entry.Header.Typeflag,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func storedNames(t testing.TB, s *EntryStore) []string {
|
||||
t.Helper()
|
||||
var names []string
|
||||
for _, entry := range s.Entries() {
|
||||
names = append(names, entry.Header.Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
// memCharge is a charge against a memory bound with disk unbounded.
|
||||
func memCharge(maxMemory int64) *Charge {
|
||||
return NewLimiter(Limits{MaxMemoryBytes: maxMemory, MaxDiskBytes: -1}).Charge()
|
||||
}
|
||||
|
||||
// diskCharge is a charge against a disk bound with memory refusing everything.
|
||||
func diskCharge(maxDisk int64) *Charge {
|
||||
return NewLimiter(Limits{MaxDiskBytes: maxDisk}).Charge()
|
||||
}
|
||||
|
||||
// filesIn lists the regular files directly in dir.
|
||||
func filesIn(t testing.TB, dir string) []string {
|
||||
t.Helper()
|
||||
entries, err := os.ReadDir(dir)
|
||||
require.NoError(t, err)
|
||||
var names []string
|
||||
for _, e := range entries {
|
||||
if !e.IsDir() {
|
||||
names = append(names, e.Name())
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// scanContext returns a context whose temp root is a fresh directory, and that directory.
|
||||
func scanContext(t testing.TB) (context.Context, string) {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
return tmpdir.WithValue(context.Background(), tmpdir.FromPath(root)), root
|
||||
}
|
||||
|
||||
// workDirsUnder returns the archive work directories directly under a scan temp root.
|
||||
func workDirsUnder(t testing.TB, root string) []string {
|
||||
t.Helper()
|
||||
children, err := os.ReadDir(root)
|
||||
require.NoError(t, err)
|
||||
var out []string
|
||||
for _, child := range children {
|
||||
if child.IsDir() && strings.HasPrefix(child.Name(), workDirName) {
|
||||
out = append(out, filepath.Join(root, child.Name()))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// extractBytes extracts an archive into a store built over the given charge.
|
||||
func extractBytes(t testing.TB, data []byte, name string, charge *Charge) (*EntryStore, bool) {
|
||||
t.Helper()
|
||||
store := storeFor(t, charge)
|
||||
content := bytes.NewReader(data)
|
||||
format := identifyFormat(context.Background(), name, content)
|
||||
require.NotNil(t, format, "fixture must identify as an archive")
|
||||
truncated, err := extractInto(context.Background(), format, content, store, nil)
|
||||
require.NoError(t, err)
|
||||
return store, truncated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user