diff --git a/cmd/trivy/main.go b/cmd/trivy/main.go index 83d2c751c4..5422a2d81e 100644 --- a/cmd/trivy/main.go +++ b/cmd/trivy/main.go @@ -41,6 +41,9 @@ func run() error { return nil } + // Ensure cleanup on exit + defer commands.Cleanup() + // Set up signal handling for graceful shutdown ctx := commands.NotifyContext(context.Background()) diff --git a/misc/lint/rules.go b/misc/lint/rules.go index 591b31d1ef..844a6a0551 100644 --- a/misc/lint/rules.go +++ b/misc/lint/rules.go @@ -35,3 +35,16 @@ func mapSet(m dsl.Matcher) { m.Match(`map[$x]struct{}`). Report("use github.com/aquasecurity/trivy/pkg/set.Set instead of map.") } + +// Enforce usage of x/os package for temporary file operations +func tempFileOps(m dsl.Matcher) { + m.Match(`os.CreateTemp($*args)`). + Where(!m.File().Name.Matches(`.*_test\.go$`)). + Suggest(`xos.CreateTemp($args)`). + Report("use github.com/aquasecurity/trivy/pkg/x/os.CreateTemp instead of os.CreateTemp for process-safe temp file cleanup") + + m.Match(`os.MkdirTemp($*args)`). + Where(!m.File().Name.Matches(`.*_test\.go$`)). + Suggest(`xos.MkdirTemp($args)`). + Report("use github.com/aquasecurity/trivy/pkg/x/os.MkdirTemp instead of os.MkdirTemp for process-safe temp file cleanup") +} diff --git a/pkg/commands/signal.go b/pkg/commands/signal.go index 45e0c542e3..7dce942e8e 100644 --- a/pkg/commands/signal.go +++ b/pkg/commands/signal.go @@ -7,6 +7,7 @@ import ( "syscall" "github.com/aquasecurity/trivy/pkg/log" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) // NotifyContext returns a context that is canceled when SIGINT or SIGTERM is received. @@ -26,7 +27,10 @@ func NotifyContext(parent context.Context) context.Context { log.Info("Received signal, attempting graceful shutdown...") log.Info("Press Ctrl+C again to force exit") - // TODO: Add any necessary cleanup logic here + // Perform cleanup + if err := Cleanup(); err != nil { + log.Debug("Failed to clean up temporary files", log.Err(err)) + } // Clean up signal handling // After calling stop(), a second signal will cause immediate termination @@ -35,3 +39,8 @@ func NotifyContext(parent context.Context) context.Context { return ctx } + +// Cleanup performs cleanup tasks before Trivy exits +func Cleanup() error { + return xos.Cleanup() +} diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go index be767e0cf2..bf578004a1 100644 --- a/pkg/dependency/parser/java/jar/parse.go +++ b/pkg/dependency/parser/java/jar/parse.go @@ -20,6 +20,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var ( @@ -198,7 +199,7 @@ func (p *Parser) parseInnerJar(zf *zip.File, rootPath string) ([]ftypes.Package, return nil, nil, xerrors.Errorf("unable to open %s: %w", zf.Name, err) } - f, err := os.CreateTemp("", "inner") + f, err := xos.CreateTemp("", "jar-inner-") if err != nil { return nil, nil, xerrors.Errorf("unable to create a temp file: %w", err) } diff --git a/pkg/downloader/download.go b/pkg/downloader/download.go index d1e37b68fe..1ad47d57af 100644 --- a/pkg/downloader/download.go +++ b/pkg/downloader/download.go @@ -17,6 +17,7 @@ import ( "golang.org/x/xerrors" xhttp "github.com/aquasecurity/trivy/pkg/x/http" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var ErrSkipDownload = errors.New("skip download") @@ -36,7 +37,7 @@ type Auth struct { // DownloadToTempDir downloads the configured source to a temp dir. func DownloadToTempDir(ctx context.Context, src string, opts Options) (string, error) { - tempDir, err := os.MkdirTemp("", "trivy-download") + tempDir, err := xos.MkdirTemp("", "download-") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } diff --git a/pkg/fanal/analyzer/fs.go b/pkg/fanal/analyzer/fs.go index 244bd5bdaa..5919d71c97 100644 --- a/pkg/fanal/analyzer/fs.go +++ b/pkg/fanal/analyzer/fs.go @@ -10,6 +10,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/mapfs" + xos "github.com/aquasecurity/trivy/pkg/x/os" "github.com/aquasecurity/trivy/pkg/x/sync" ) @@ -20,7 +21,7 @@ type CompositeFS struct { } func NewCompositeFS() (*CompositeFS, error) { - tmpDir, err := os.MkdirTemp("", "analyzer-fs-*") + tmpDir, err := xos.MkdirTemp("", "analyzer-composite-") if err != nil { return nil, xerrors.Errorf("unable to create temporary directory: %w", err) } @@ -35,7 +36,7 @@ func NewCompositeFS() (*CompositeFS, error) { func (c *CompositeFS) CopyFileToTemp(opener Opener, _ os.FileInfo) (string, error) { // Create a temporary file to which the file in the layer will be copied // so that all the files will not be loaded into memory - f, err := os.CreateTemp(c.dir, "file-*") + f, err := xos.CreateTemp("", "analyzer-file-") if err != nil { return "", xerrors.Errorf("create temp error: %w", err) } diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm.go b/pkg/fanal/analyzer/pkg/rpm/rpm.go index 9032e593ca..bad79bff7d 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpm.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpm.go @@ -18,6 +18,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) func init() { @@ -261,7 +262,7 @@ func packageProvidedByVendor(pkg *rpmdb.PackageInfo) bool { } func writeToTempFile(rc io.Reader) (string, error) { - tmpDir, err := os.MkdirTemp("", "rpm") + tmpDir, err := xos.MkdirTemp("", "rpmdb-") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 5af4b24b0b..01c04dd158 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -29,6 +29,7 @@ import ( "github.com/aquasecurity/trivy/pkg/semaphore" trivyTypes "github.com/aquasecurity/trivy/pkg/types" xio "github.com/aquasecurity/trivy/pkg/x/io" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) const artifactVersion = 1 @@ -64,7 +65,7 @@ func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (a return nil, xerrors.Errorf("config analyzer group error: %w", err) } - cacheDir, err := os.MkdirTemp("", "layers") + cacheDir, err := xos.MkdirTemp("", "image-layers-") if err != nil { return nil, xerrors.Errorf("failed to create a cache layers temp dir: %w", err) } diff --git a/pkg/fanal/artifact/image/remote_sbom.go b/pkg/fanal/artifact/image/remote_sbom.go index d07d9cafe3..a8fcde86a3 100644 --- a/pkg/fanal/artifact/image/remote_sbom.go +++ b/pkg/fanal/artifact/image/remote_sbom.go @@ -21,6 +21,7 @@ import ( "github.com/aquasecurity/trivy/pkg/oci" "github.com/aquasecurity/trivy/pkg/remote" "github.com/aquasecurity/trivy/pkg/types" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var errNoSBOMFound = xerrors.New("remote SBOM not found") @@ -88,9 +89,9 @@ func (a Artifact) parseReferrer(ctx context.Context, repo string, desc v1.Descri const fileName string = "referrer.sbom" repoName := fmt.Sprintf("%s@%s", repo, desc.Digest) - tmpDir, err := os.MkdirTemp("", "trivy-sbom-*") + tmpDir, err := xos.MkdirTemp("", "sbom-referrer-") if err != nil { - return artifact.Reference{}, xerrors.Errorf("mkdir temp error: %w", err) + return artifact.Reference{}, xerrors.Errorf("failed to create temp directory: %w", err) } defer os.RemoveAll(tmpDir) @@ -133,7 +134,7 @@ func (a Artifact) inspectRekorSBOMAttestation(ctx context.Context) (artifact.Ref return artifact.Reference{}, xerrors.Errorf("failed to retrieve SBOM attestation: %w", err) } - f, err := os.CreateTemp("", "sbom-*") + f, err := xos.CreateTemp("", "sbom-attestation-") if err != nil { return artifact.Reference{}, xerrors.Errorf("failed to create a temporary file: %w", err) } diff --git a/pkg/fanal/artifact/repo/git.go b/pkg/fanal/artifact/repo/git.go index 0bd96be423..860c828e27 100644 --- a/pkg/fanal/artifact/repo/git.go +++ b/pkg/fanal/artifact/repo/git.go @@ -16,6 +16,7 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/artifact/local" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/fanal/walker" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var ( @@ -93,7 +94,7 @@ func tryRemoteRepo(target string, c cache.ArtifactCache, w Walker, artifactOpt a } func cloneRepo(u *url.URL, artifactOpt artifact.Option) (string, error) { - tmpDir, err := os.MkdirTemp("", "trivy-remote-repo") + tmpDir, err := xos.MkdirTemp("", "git-clone-") if err != nil { return "", xerrors.Errorf("failed to create a temp dir: %w", err) } diff --git a/pkg/fanal/image/daemon/containerd.go b/pkg/fanal/image/daemon/containerd.go index cd19fcbe44..5ca457eb80 100644 --- a/pkg/fanal/image/daemon/containerd.go +++ b/pkg/fanal/image/daemon/containerd.go @@ -29,6 +29,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) const ( @@ -130,7 +131,7 @@ func ContainerdImage(ctx context.Context, imageName string, opts types.ImageOpti img := imgs[0] - f, err := os.CreateTemp("", "fanal-containerd-*") + f, err := xos.CreateTemp("", "containerd-export-") if err != nil { return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) } diff --git a/pkg/fanal/image/daemon/context_private_test_windows.go b/pkg/fanal/image/daemon/context_private_test_windows.go index 4a17fabf08..02b7890084 100644 --- a/pkg/fanal/image/daemon/context_private_test_windows.go +++ b/pkg/fanal/image/daemon/context_private_test_windows.go @@ -2,8 +2,8 @@ package daemon const ( testContextHost = "npipe:////./pipe/test_docker_engine" - + // Test socket paths for Windows systems - testFlagHost = "npipe:////./pipe/flag_docker" - testEnvHost = "npipe:////./pipe/env_docker" -) \ No newline at end of file + testFlagHost = "npipe:////./pipe/flag_docker" + testEnvHost = "npipe:////./pipe/env_docker" +) diff --git a/pkg/fanal/image/daemon/docker.go b/pkg/fanal/image/daemon/docker.go index 96e54f7083..c727fb8fca 100644 --- a/pkg/fanal/image/daemon/docker.go +++ b/pkg/fanal/image/daemon/docker.go @@ -7,6 +7,8 @@ import ( "github.com/docker/docker/client" "github.com/google/go-containerregistry/pkg/name" "golang.org/x/xerrors" + + xos "github.com/aquasecurity/trivy/pkg/x/os" ) // DockerImage implements v1.Image by extending daemon.Image. @@ -56,7 +58,7 @@ func DockerImage(ref name.Reference, host string) (Image, func(), error) { return nil, cleanup, xerrors.Errorf("unable to get history (%s): %w", imageID, err) } - f, err := os.CreateTemp("", "fanal-*") + f, err := xos.CreateTemp("", "docker-export-") if err != nil { return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) } diff --git a/pkg/fanal/image/daemon/podman.go b/pkg/fanal/image/daemon/podman.go index 49a7357d04..effa725e05 100644 --- a/pkg/fanal/image/daemon/podman.go +++ b/pkg/fanal/image/daemon/podman.go @@ -14,6 +14,8 @@ import ( dimage "github.com/docker/docker/api/types/image" "github.com/docker/docker/client" "golang.org/x/xerrors" + + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var ( @@ -130,7 +132,7 @@ func PodmanImage(ref, host string) (Image, func(), error) { return nil, cleanup, xerrors.Errorf("unable to inspect the image (%s): %w", ref, err) } - f, err := os.CreateTemp("", "fanal-*") + f, err := xos.CreateTemp("", "podman-export-") if err != nil { return nil, cleanup, xerrors.Errorf("failed to create a temporary file: %w", err) } diff --git a/pkg/fanal/walker/cached_file.go b/pkg/fanal/walker/cached_file.go index fc963c0104..20184e5b6e 100644 --- a/pkg/fanal/walker/cached_file.go +++ b/pkg/fanal/walker/cached_file.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" xio "github.com/aquasecurity/trivy/pkg/x/io" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) // cachedFile represents a file cached in memory or storage according to the file size. @@ -37,7 +38,7 @@ func (o *cachedFile) Open() (xio.ReadSeekCloserAt, error) { o.once.Do(func() { // When the file is large, it will be written down to a temp file. if o.size >= defaultSizeThreshold { - f, err := os.CreateTemp("", "fanal-*") + f, err := xos.CreateTemp("", "cached-file-") if err != nil { o.err = xerrors.Errorf("failed to create the temp file: %w", err) return diff --git a/pkg/k8s/scanner/io.go b/pkg/k8s/scanner/io.go index 65f102a4b0..1aeaec9a45 100644 --- a/pkg/k8s/scanner/io.go +++ b/pkg/k8s/scanner/io.go @@ -12,6 +12,7 @@ import ( "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" "github.com/aquasecurity/trivy/pkg/log" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) var r = regexp.MustCompile("[\\\\/:*?<>]") @@ -22,7 +23,7 @@ func generateTempFileByArtifact(artifact *artifacts.Artifact, tempDir string) (s // removes characters not permitted in file/directory names on Windows filename = filenameWindowsFriendly(filename) } - file, err := os.CreateTemp(tempDir, filename) + file, err := xos.CreateTemp(tempDir, filename) if err != nil { return "", xerrors.Errorf("failed to create temporary file: %w", err) } @@ -45,7 +46,7 @@ func generateTempFileByArtifact(artifact *artifacts.Artifact, tempDir string) (s // generateTempDir creates a directory with yaml files generated from kubernetes artifacts // returns a directory name, a map for mapping a temp target file to k8s artifact and error func generateTempDir(arts []*artifacts.Artifact) (string, map[string]*artifacts.Artifact, error) { - tempDir, err := os.MkdirTemp("", "trivyk8s*") + tempDir, err := xos.MkdirTemp("", "k8s-scan-") if err != nil { return "", nil, xerrors.Errorf("failed to create temp directory: %w", err) } diff --git a/pkg/oci/artifact.go b/pkg/oci/artifact.go index 09e9ff8d68..9647bba59a 100644 --- a/pkg/oci/artifact.go +++ b/pkg/oci/artifact.go @@ -22,6 +22,7 @@ import ( "github.com/aquasecurity/trivy/pkg/remote" "github.com/aquasecurity/trivy/pkg/version/doc" xio "github.com/aquasecurity/trivy/pkg/x/io" + xos "github.com/aquasecurity/trivy/pkg/x/os" ) const ( @@ -174,7 +175,7 @@ func (a *Artifact) download(ctx context.Context, layer v1.Layer, fileName, dir s defer bar.Finish() // https://github.com/hashicorp/go-getter/issues/326 - tempDir, err := os.MkdirTemp("", "trivy") + tempDir, err := xos.MkdirTemp("", "oci-download-") if err != nil { return xerrors.Errorf("failed to create a temp dir: %w", err) } diff --git a/pkg/rpc/server/listen.go b/pkg/rpc/server/listen.go index 10e6d814ff..e60f4fd034 100644 --- a/pkg/rpc/server/listen.go +++ b/pkg/rpc/server/listen.go @@ -22,6 +22,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" "github.com/aquasecurity/trivy/pkg/version" + xos "github.com/aquasecurity/trivy/pkg/x/os" rpcCache "github.com/aquasecurity/trivy/rpc/cache" rpcScanner "github.com/aquasecurity/trivy/rpc/scanner" ) @@ -189,7 +190,7 @@ func (w dbWorker) update(ctx context.Context, appVersion, dbDir string, } func (w dbWorker) hotUpdate(ctx context.Context, dbDir string, dbUpdateWg, requestWg *sync.WaitGroup, opt types.RegistryOptions) error { - tmpDir, err := os.MkdirTemp("", "db") + tmpDir, err := xos.MkdirTemp("", "db-worker-") if err != nil { return xerrors.Errorf("failed to create a temp dir: %w", err) } diff --git a/pkg/x/os/os.go b/pkg/x/os/os.go new file mode 100644 index 0000000000..269fafa50a --- /dev/null +++ b/pkg/x/os/os.go @@ -0,0 +1,88 @@ +package os + +import ( + "fmt" + "os" + "path/filepath" + "sync" + "sync/atomic" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/log" +) + +var ( + tempDirOnce = sync.OnceValues(initTempDir) + // initialized tracks whether the temp directory has been created. + // This is used by Cleanup() to avoid creating a directory just to delete it. + initialized atomic.Bool +) + +// initTempDir initializes the process-specific temp directory +func initTempDir() (string, error) { + pid := os.Getpid() + tempDir := filepath.Join(os.TempDir(), fmt.Sprintf("trivy-%d", pid)) + + if err := os.MkdirAll(tempDir, 0o755); err != nil { + return "", xerrors.Errorf("failed to create temp dir: %w", err) + } + + log.Debug("Created process-specific temp directory", log.String("path", tempDir)) + initialized.Store(true) + return tempDir, nil +} + +// CreateTemp creates a temporary file, using the process-specific directory when dir is empty +func CreateTemp(dir, pattern string) (*os.File, error) { + // If dir is empty, use our process-specific temp directory + if dir == "" { + tempDir, err := tempDirOnce() + if err != nil { + return nil, err + } + dir = tempDir + } + + return os.CreateTemp(dir, pattern) //nolint: gocritic +} + +// MkdirTemp creates a temporary directory, using the process-specific directory as base when dir is empty +func MkdirTemp(dir, pattern string) (string, error) { + // If dir is empty, use our process-specific temp directory + if dir == "" { + tempDir, err := tempDirOnce() + if err != nil { + return "", err + } + dir = tempDir + } + + return os.MkdirTemp(dir, pattern) //nolint: gocritic +} + +// TempDir returns the process-specific temp directory path +func TempDir() string { + tempDir, err := tempDirOnce() + if err != nil { + log.Debug("Failed to get process-specific temp directory, falling back to system temp", log.Err(err)) + return os.TempDir() // fallback + } + return tempDir +} + +// Cleanup removes the entire process-specific temp directory +// Note: On Windows, directory deletion may fail if files are still open +func Cleanup() error { + // If temp dir was never initialized, nothing to clean up + if !initialized.Load() { + return nil + } + + tempDir, err := tempDirOnce() + if err != nil || tempDir == "" { + return nil + } + log.Debug("Cleaning up temp directory", log.String("path", tempDir)) + return os.RemoveAll(tempDir) +} diff --git a/pkg/x/os/os_test.go b/pkg/x/os/os_test.go new file mode 100644 index 0000000000..236ba32355 --- /dev/null +++ b/pkg/x/os/os_test.go @@ -0,0 +1,175 @@ +package os + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// resetForTest resets global variables for testing +func resetForTest() { + tempDirOnce = sync.OnceValues(initTempDir) +} + +func TestTempDir(t *testing.T) { + resetForTest() + t.Cleanup(func() { + _ = Cleanup() + resetForTest() + }) + + got := TempDir() + + // Should contain process ID + pid := os.Getpid() + want := filepath.Join(os.TempDir(), "trivy-"+strconv.Itoa(pid)) + assert.Equal(t, want, got) + + // Directory should exist + _, err := os.Stat(got) + require.NoError(t, err) +} + +func TestCreateTemp(t *testing.T) { + resetForTest() + + tests := []struct { + name string + pattern string + }{ + { + name: "simple pattern", + pattern: "test-", + }, + { + name: "empty pattern", + pattern: "", + }, + { + name: "pattern with extension", + pattern: "test-*.txt", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Test with empty dir (should use process-specific dir) + file, err := CreateTemp("", tt.pattern) //nolint: usetesting + require.NoError(t, err) + t.Cleanup(func() { + _ = file.Close() + _ = Cleanup() + resetForTest() + }) + + // File should exist + _, err = os.Stat(file.Name()) + require.NoError(t, err) + + // File should be in our temp directory + pid := os.Getpid() + expectedDir := filepath.Join(os.TempDir(), "trivy-"+strconv.Itoa(pid)) + assert.True(t, strings.HasPrefix(file.Name(), expectedDir)) + + // Test with specific dir + customDir := t.TempDir() + file2, err := CreateTemp(customDir, tt.pattern) + require.NoError(t, err) + defer file2.Close() + + // File should exist and be in custom dir + _, err = os.Stat(file2.Name()) + require.NoError(t, err) + assert.True(t, strings.HasPrefix(file2.Name(), customDir)) + }) + } +} + +func TestMkdirTemp(t *testing.T) { + resetForTest() + + tests := []struct { + name string + pattern string + }{ + { + name: "simple pattern", + pattern: "test-", + }, + { + name: "empty pattern", + pattern: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Cleanup(func() { + Cleanup() + resetForTest() + }) + + // Test with empty dir (should use process-specific dir) + dir, err := MkdirTemp("", tt.pattern) //nolint:usetesting + require.NoError(t, err) + + // Directory should exist + _, err = os.Stat(dir) + require.NoError(t, err) + + // Directory should be in our temp directory + wantParent := filepath.Join(os.TempDir(), "trivy-"+strconv.Itoa(os.Getpid())) + assert.True(t, strings.HasPrefix(dir, wantParent)) + + // Test with specific dir + customParent := t.TempDir() + dir2, err := MkdirTemp(customParent, tt.pattern) //nolint:usetesting + require.NoError(t, err) + + // Directory should exist and be in custom parent + _, err = os.Stat(dir2) + require.NoError(t, err) + assert.True(t, strings.HasPrefix(dir2, customParent)) + }) + } +} + +func TestCleanup(t *testing.T) { + resetForTest() + t.Cleanup(func() { + resetForTest() + }) + + // Create a temp file + file, err := CreateTemp("", "test-") //nolint: usetesting + require.NoError(t, err) + filename := file.Name() + require.NoError(t, file.Close()) + + // Directory should exist + dir := filepath.Join(os.TempDir(), "trivy-"+strconv.Itoa(os.Getpid())) + _, err = os.Stat(dir) + require.NoError(t, err) + + // File should exist + _, err = os.Stat(filename) + require.NoError(t, err) + + // Cleanup + err = Cleanup() + require.NoError(t, err) + + // File should be gone + _, err = os.Stat(filename) + require.ErrorIs(t, err, os.ErrNotExist) + + // Directory should be gone + _, err = os.Stat(dir) + assert.ErrorIs(t, err, os.ErrNotExist) +}