mirror of
https://github.com/anchore/syft.git
synced 2026-10-24 12:02:22 +02:00
Compare commits
+3
-3
@@ -29,7 +29,7 @@ tools:
|
||||
|
||||
- name: cosign
|
||||
version:
|
||||
want: v2.2.0
|
||||
want: v2.2.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: sigstore/cosign
|
||||
@@ -43,7 +43,7 @@ tools:
|
||||
|
||||
- name: goreleaser
|
||||
version:
|
||||
want: v1.22.0
|
||||
want: v1.22.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: goreleaser/goreleaser
|
||||
@@ -90,7 +90,7 @@ tools:
|
||||
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.38.0
|
||||
want: v2.39.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 #v2.22.5
|
||||
uses: github/codeql-action/init@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 #v2.22.5
|
||||
uses: github/codeql-action/autobuild@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 #v2.22.5
|
||||
uses: github/codeql-action/analyze@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
|
||||
@@ -168,6 +168,7 @@ You can override the list of enabled/disabled catalogers by using the "cataloger
|
||||
- apkdb
|
||||
- binary
|
||||
- dotnet-deps
|
||||
- dotnet-portable-executable
|
||||
- dpkgdb
|
||||
- go-module-binary
|
||||
- graalvm-native-image
|
||||
@@ -190,6 +191,7 @@ You can override the list of enabled/disabled catalogers by using the "cataloger
|
||||
- conan
|
||||
- dartlang-lock
|
||||
- dotnet-deps
|
||||
- dotnet-portable-executable
|
||||
- dpkgdb
|
||||
- elixir-mix-lock
|
||||
- erlang-rebar-lock
|
||||
@@ -502,6 +504,7 @@ platform: ""
|
||||
# - conan-cataloger
|
||||
# - dartlang-lock-cataloger
|
||||
# - dotnet-deps-cataloger
|
||||
# - dotnet-portable-executable-cataloger
|
||||
# - dpkg-db-cataloger
|
||||
# - elixir-mix-lock-cataloger
|
||||
# - erlang-rebar-lock-cataloger
|
||||
|
||||
+7
-6
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
|
||||
@@ -22,18 +23,18 @@ import (
|
||||
// It also constructs the syft attest command and the syft version command.
|
||||
// `RunE` is the earliest that the complete application configuration can be loaded.
|
||||
func Application(id clio.Identification) clio.Application {
|
||||
app, _ := create(id)
|
||||
return app
|
||||
app, rootCmd := create(id, os.Stdout)
|
||||
return ui.StdoutLoggingApplication(app, rootCmd)
|
||||
}
|
||||
|
||||
// Command returns the root command for the syft CLI application. This is useful for embedding the entire syft CLI
|
||||
// into an existing application.
|
||||
func Command(id clio.Identification) *cobra.Command {
|
||||
_, cmd := create(id)
|
||||
_, cmd := create(id, os.Stdout)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func create(id clio.Identification) (clio.Application, *cobra.Command) {
|
||||
func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
|
||||
clioCfg := clio.NewSetupConfig(id).
|
||||
WithGlobalConfigFlag(). // add persistent -c <path> for reading an application config from
|
||||
WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
|
||||
@@ -41,13 +42,13 @@ func create(id clio.Identification) (clio.Application, *cobra.Command) {
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
noUI := ui.None(cfg.Log.Quiet)
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
ui.New(cfg.Log.Quiet,
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
handler.New(handler.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
[Test_writeEvents/no_events/stdout - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_writeEvents/no_events/stderr - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_writeEvents/all_events/stdout - 1]
|
||||
|
||||
|
||||
<my --
|
||||
-
|
||||
-
|
||||
report 1!!>
|
||||
<report 2>
|
||||
|
||||
---
|
||||
|
||||
[Test_writeEvents/all_events/stderr - 1]
|
||||
|
||||
|
||||
<my notification 1!!
|
||||
...still notifying>
|
||||
|
||||
|
||||
<notification 2>
|
||||
<notification 3>
|
||||
A newer version of syft is available for download: v0.33.0 (installed version is [not provided])
|
||||
|
||||
---
|
||||
|
||||
[Test_writeEvents/quiet_only_shows_report/stdout - 1]
|
||||
<report 1>
|
||||
|
||||
---
|
||||
|
||||
[Test_writeEvents/quiet_only_shows_report/stderr - 1]
|
||||
|
||||
---
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
[Test_postUIEventWriter_write/no_events/stdout - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/no_events/stderr - 1]
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/all_events/stdout - 1]
|
||||
|
||||
|
||||
<my --
|
||||
-
|
||||
-
|
||||
report 1!!>
|
||||
<report 2>
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/all_events/stderr - 1]
|
||||
|
||||
|
||||
<my notification 1!!
|
||||
...still notifying>
|
||||
|
||||
|
||||
<notification 2>
|
||||
<notification 3>
|
||||
A newer version of syft is available for download: v0.33.0 (installed version is [not provided])
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/quiet_only_shows_report/stdout - 1]
|
||||
<report 1>
|
||||
|
||||
---
|
||||
|
||||
[Test_postUIEventWriter_write/quiet_only_shows_report/stderr - 1]
|
||||
|
||||
---
|
||||
@@ -0,0 +1,61 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// capture replaces the provided *os.File and redirects output to the provided writer. The return value is a function,
|
||||
// which is used to stop the current capturing of output and restore the original file.
|
||||
// Example:
|
||||
//
|
||||
// restore := capture(&os.Stderr, writer)
|
||||
// // here, stderr will be captured and redirected to the provided writer
|
||||
// restore() // block until the output has all been sent to the writer and restore the original stderr
|
||||
func capture(target **os.File, writer io.Writer, bufSize int) (close func()) {
|
||||
original := *target
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
|
||||
*target = w
|
||||
|
||||
done := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
buf := make([]byte, bufSize)
|
||||
for {
|
||||
if original == nil {
|
||||
break
|
||||
}
|
||||
|
||||
n, err := r.Read(buf)
|
||||
if n > 0 {
|
||||
_, _ = writer.Write(buf[0:n])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
if original != nil {
|
||||
_ = w.Close()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(1 * time.Second):
|
||||
log.Debugf("stdout buffer timed out after 1 second")
|
||||
}
|
||||
*target = original
|
||||
original = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_capture(t *testing.T) {
|
||||
r, w, _ := os.Pipe()
|
||||
t.Logf("pipe1: %+v", w)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf2 := &bytes.Buffer{}
|
||||
|
||||
go func() {
|
||||
// write to the main file (e.g. os.Stdout)
|
||||
_, _ = w.WriteString("write1")
|
||||
|
||||
// capture the output to the provided buffer
|
||||
restoreInitial := capture(&w, buf, 1024)
|
||||
t.Logf("pipe2: %+v", w)
|
||||
_, _ = w.WriteString("write2")
|
||||
|
||||
// capture output nested
|
||||
restoreFirstCapture := capture(&w, buf2, 1024)
|
||||
t.Logf("pipe3: %+v", w)
|
||||
_, _ = w.WriteString("write3")
|
||||
|
||||
// discard file used to write the "write3"
|
||||
restoreFirstCapture()
|
||||
|
||||
// restore should block until all output has been captured, so it's safe to read buf2 here
|
||||
require.Equal(t, "write3", buf2.String())
|
||||
|
||||
// restore should be safe to call multiple times
|
||||
restoreFirstCapture()
|
||||
require.Equal(t, "write3", buf2.String())
|
||||
|
||||
// write again to the initial buffer
|
||||
t.Logf("pipe2+: %+v", w)
|
||||
_, _ = w.WriteString("write2+")
|
||||
|
||||
// restore the initial file (e.g. os.Stdout) and write more to it
|
||||
restoreInitial()
|
||||
t.Logf("pipe1+: %+v", w)
|
||||
_, _ = w.WriteString("write1+")
|
||||
|
||||
// close the pipe to continue with the io.ReadAll, below
|
||||
_ = w.Close()
|
||||
}()
|
||||
|
||||
got, err := io.ReadAll(r)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "write1write1+", string(got))
|
||||
|
||||
require.Equal(t, "write2write2+", buf.String())
|
||||
}
|
||||
|
||||
func Test_captureBufSizes(t *testing.T) {
|
||||
_, w, _ := os.Pipe()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
restore := capture(&w, buf, 200)
|
||||
|
||||
line := "line1\nline2\nline3"
|
||||
|
||||
_, err := w.WriteString(line)
|
||||
require.NoError(t, err)
|
||||
|
||||
restore()
|
||||
require.Equal(t, line, buf.String())
|
||||
|
||||
buf.Reset()
|
||||
restore = capture(&w, buf, 2)
|
||||
|
||||
_, err = w.WriteString(line)
|
||||
require.NoError(t, err)
|
||||
|
||||
restore()
|
||||
require.Equal(t, line, buf.String())
|
||||
}
|
||||
+25
-37
@@ -14,47 +14,35 @@ import (
|
||||
"github.com/anchore/syft/syft/event/parsers"
|
||||
)
|
||||
|
||||
type postUIEventWriter struct {
|
||||
handles []postUIHandle
|
||||
}
|
||||
|
||||
type postUIHandle struct {
|
||||
respectQuiet bool
|
||||
event partybus.EventType
|
||||
writer io.Writer
|
||||
dispatch eventWriter
|
||||
}
|
||||
|
||||
type eventWriter func(io.Writer, ...partybus.Event) error
|
||||
|
||||
func newPostUIEventWriter(stdout, stderr io.Writer) *postUIEventWriter {
|
||||
return &postUIEventWriter{
|
||||
handles: []postUIHandle{
|
||||
{
|
||||
event: event.CLIReport,
|
||||
respectQuiet: false,
|
||||
writer: stdout,
|
||||
dispatch: writeReports,
|
||||
},
|
||||
{
|
||||
event: event.CLINotification,
|
||||
respectQuiet: true,
|
||||
writer: stderr,
|
||||
dispatch: writeNotifications,
|
||||
},
|
||||
{
|
||||
event: event.CLIAppUpdateAvailable,
|
||||
respectQuiet: true,
|
||||
writer: stderr,
|
||||
dispatch: writeAppUpdate,
|
||||
},
|
||||
func writeEvents(out, err io.Writer, quiet bool, events ...partybus.Event) error {
|
||||
handles := []struct {
|
||||
event partybus.EventType
|
||||
respectQuiet bool
|
||||
writer io.Writer
|
||||
dispatch func(writer io.Writer, events ...partybus.Event) error
|
||||
}{
|
||||
{
|
||||
event: event.CLIReport,
|
||||
respectQuiet: false,
|
||||
writer: out,
|
||||
dispatch: writeReports,
|
||||
},
|
||||
{
|
||||
event: event.CLINotification,
|
||||
respectQuiet: true,
|
||||
writer: err,
|
||||
dispatch: writeNotifications,
|
||||
},
|
||||
{
|
||||
event: event.CLIAppUpdateAvailable,
|
||||
respectQuiet: true,
|
||||
writer: err,
|
||||
dispatch: writeAppUpdate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w postUIEventWriter) write(quiet bool, events ...partybus.Event) error {
|
||||
var errs error
|
||||
for _, h := range w.handles {
|
||||
for _, h := range handles {
|
||||
if quiet && h.respectQuiet {
|
||||
continue
|
||||
}
|
||||
+3
-4
@@ -12,8 +12,7 @@ import (
|
||||
"github.com/anchore/syft/syft/event/parsers"
|
||||
)
|
||||
|
||||
func Test_postUIEventWriter_write(t *testing.T) {
|
||||
|
||||
func Test_writeEvents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
quiet bool
|
||||
@@ -86,9 +85,9 @@ func Test_postUIEventWriter_write(t *testing.T) {
|
||||
|
||||
stdout := &bytes.Buffer{}
|
||||
stderr := &bytes.Buffer{}
|
||||
w := newPostUIEventWriter(stdout, stderr)
|
||||
|
||||
tt.wantErr(t, w.write(tt.quiet, tt.events...))
|
||||
err := writeEvents(stdout, stderr, tt.quiet, tt.events...)
|
||||
tt.wantErr(t, err)
|
||||
|
||||
t.Run("stdout", func(t *testing.T) {
|
||||
snaps.MatchSnapshot(t, stdout.String())
|
||||
@@ -0,0 +1,37 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
func newLogWriter() io.Writer {
|
||||
l := logWriter{}
|
||||
l.r = bufio.NewReader(&l.buf)
|
||||
return &l
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
buf bytes.Buffer
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
func (l *logWriter) Write(data []byte) (n int, err error) {
|
||||
l.buf.Write(data)
|
||||
s, err := l.r.ReadString('\n')
|
||||
s = strings.TrimRight(s, "\n")
|
||||
for s != "" {
|
||||
log.Trace(s)
|
||||
n += len(s)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
s, err = l.r.ReadString('\n')
|
||||
s = strings.TrimRight(s, "\n")
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/go-logger"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
func Test_logWriter(t *testing.T) {
|
||||
w := newLogWriter()
|
||||
|
||||
orig := log.Get()
|
||||
t.Cleanup(func() {
|
||||
log.Set(orig)
|
||||
})
|
||||
|
||||
bl := &bufferLogger{}
|
||||
log.Set(bl)
|
||||
|
||||
_, _ = w.Write([]byte("a\nvalue"))
|
||||
|
||||
expected := []any{"a", "value"}
|
||||
require.Equal(t, expected, bl.values)
|
||||
|
||||
bl.values = nil
|
||||
_, _ = w.Write([]byte("some"))
|
||||
_, _ = w.Write([]byte("thing"))
|
||||
|
||||
expected = []any{"some", "thing"}
|
||||
require.Equal(t, expected, bl.values)
|
||||
}
|
||||
|
||||
type bufferLogger struct{ values []any }
|
||||
|
||||
func (l *bufferLogger) Tracef(_ string, _ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Debugf(_ string, _ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Infof(_ string, _ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Warnf(_ string, _ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Errorf(_ string, _ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Trace(vals ...interface{}) {
|
||||
l.values = append(l.values, vals...)
|
||||
}
|
||||
|
||||
func (l *bufferLogger) Debug(_ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Info(_ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) Warn(vals ...interface{}) {
|
||||
l.values = append(l.values, vals...)
|
||||
}
|
||||
|
||||
func (l *bufferLogger) Error(_ ...interface{}) {}
|
||||
|
||||
func (l *bufferLogger) WithFields(_ ...interface{}) logger.MessageLogger { return l }
|
||||
|
||||
func (l *bufferLogger) Nested(_ ...interface{}) logger.Logger { return l }
|
||||
|
||||
func (l *bufferLogger) SetOutput(_ io.Writer) {}
|
||||
|
||||
func (l *bufferLogger) GetOutput() io.Writer { return nil }
|
||||
@@ -1,6 +1,7 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/wagoodman/go-partybus"
|
||||
@@ -12,13 +13,17 @@ import (
|
||||
var _ clio.UI = (*NoUI)(nil)
|
||||
|
||||
type NoUI struct {
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
finalizeEvents []partybus.Event
|
||||
subscription partybus.Unsubscribable
|
||||
quiet bool
|
||||
}
|
||||
|
||||
func None(quiet bool) *NoUI {
|
||||
func None(out io.Writer, quiet bool) *NoUI {
|
||||
return &NoUI{
|
||||
out: out,
|
||||
err: os.Stderr,
|
||||
quiet: quiet,
|
||||
}
|
||||
}
|
||||
@@ -38,5 +43,5 @@ func (n *NoUI) Handle(e partybus.Event) error {
|
||||
}
|
||||
|
||||
func (n NoUI) Teardown(_ bool) error {
|
||||
return newPostUIEventWriter(os.Stdout, os.Stderr).write(n.quiet, n.finalizeEvents...)
|
||||
return writeEvents(n.out, n.err, n.quiet, n.finalizeEvents...)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
)
|
||||
|
||||
const defaultStdoutLogBufferSize = 1024
|
||||
|
||||
// StdoutLoggingApplication wraps the provided app in a clio.Application, which captures non-report data
|
||||
// written to os.Stdout and instead logs it to the internal logger. It also modifies the rootCmd help function
|
||||
// to restore os.Stdout in order for Cobra to properly print help to stdout
|
||||
func StdoutLoggingApplication(app clio.Application, rootCmd *cobra.Command) clio.Application {
|
||||
return &stdoutLoggingApplication{
|
||||
delegate: app,
|
||||
rootCmd: rootCmd,
|
||||
}
|
||||
}
|
||||
|
||||
// stdoutLoggingApplication is a clio.Application, which captures data written to os.Stdout prior to the report
|
||||
// being output and instead sends non-report output to the internal logger
|
||||
type stdoutLoggingApplication struct {
|
||||
delegate clio.Application
|
||||
rootCmd *cobra.Command
|
||||
}
|
||||
|
||||
func (s *stdoutLoggingApplication) ID() clio.Identification {
|
||||
return s.delegate.ID()
|
||||
}
|
||||
|
||||
func (s *stdoutLoggingApplication) AddFlags(flags *pflag.FlagSet, cfgs ...any) {
|
||||
s.delegate.AddFlags(flags, cfgs...)
|
||||
}
|
||||
|
||||
func (s *stdoutLoggingApplication) SetupCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command {
|
||||
return s.delegate.SetupCommand(cmd, cfgs...)
|
||||
}
|
||||
|
||||
func (s *stdoutLoggingApplication) SetupRootCommand(cmd *cobra.Command, cfgs ...any) *cobra.Command {
|
||||
return s.delegate.SetupRootCommand(cmd, cfgs...)
|
||||
}
|
||||
|
||||
func (s *stdoutLoggingApplication) Run() {
|
||||
// capture everything written to stdout that is not report output
|
||||
restoreStdout := capture(&os.Stdout, newLogWriter(), defaultStdoutLogBufferSize)
|
||||
defer restoreStdout()
|
||||
|
||||
// need to restore stdout for cobra to properly output help text to the user on stdout
|
||||
baseHelpFunc := s.rootCmd.HelpFunc()
|
||||
defer s.rootCmd.SetHelpFunc(baseHelpFunc)
|
||||
s.rootCmd.SetHelpFunc(func(command *cobra.Command, strings []string) {
|
||||
restoreStdout()
|
||||
baseHelpFunc(command, strings)
|
||||
})
|
||||
|
||||
s.delegate.Run()
|
||||
}
|
||||
|
||||
var _ clio.Application = (*stdoutLoggingApplication)(nil)
|
||||
@@ -2,6 +2,7 @@ package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -25,6 +26,8 @@ var _ interface {
|
||||
} = (*UI)(nil)
|
||||
|
||||
type UI struct {
|
||||
out io.Writer
|
||||
err io.Writer
|
||||
program *tea.Program
|
||||
running *sync.WaitGroup
|
||||
quiet bool
|
||||
@@ -35,8 +38,10 @@ type UI struct {
|
||||
frame tea.Model
|
||||
}
|
||||
|
||||
func New(quiet bool, handlers ...bubbly.EventHandler) *UI {
|
||||
func New(out io.Writer, quiet bool, handlers ...bubbly.EventHandler) *UI {
|
||||
return &UI{
|
||||
out: out,
|
||||
err: os.Stderr,
|
||||
handler: bubbly.NewHandlerCollection(handlers...),
|
||||
frame: frame.New(),
|
||||
running: &sync.WaitGroup{},
|
||||
@@ -99,8 +104,7 @@ func (m *UI) Teardown(force bool) error {
|
||||
|
||||
// TODO: allow for writing out the full log output to the screen (only a partial log is shown currently)
|
||||
// this needs coordination to know what the last frame event is to change the state accordingly (which isn't possible now)
|
||||
|
||||
return newPostUIEventWriter(os.Stdout, os.Stderr).write(m.quiet, m.finalizeEvents...)
|
||||
return writeEvents(m.out, m.err, m.quiet, m.finalizeEvents...)
|
||||
}
|
||||
|
||||
// bubbletea.Model functions
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.2.0
|
||||
github.com/gkampitakis/go-snaps v0.4.11
|
||||
github.com/gkampitakis/go-snaps v0.4.12
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.10.0
|
||||
github.com/go-test/deep v1.1.0
|
||||
@@ -62,7 +62,7 @@ require (
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/spdx/tools-golang v0.5.3
|
||||
github.com/spf13/afero v1.10.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
github.com/vifraa/gopom v1.0.0
|
||||
@@ -71,11 +71,13 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/net v0.18.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.27.0
|
||||
)
|
||||
|
||||
require github.com/spf13/pflag v1.0.5
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
@@ -120,7 +122,7 @@ require (
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.2.5 // indirect
|
||||
github.com/gkampitakis/ciinfo v0.3.0 // indirect
|
||||
github.com/gkampitakis/go-diff v1.3.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
@@ -147,6 +149,7 @@ require (
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/maruel/natural v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 // indirect
|
||||
@@ -182,13 +185,12 @@ require (
|
||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/sylabs/sif/v2 v2.11.5 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.16.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
@@ -203,11 +205,11 @@ require (
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||
|
||||
@@ -202,6 +202,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
@@ -275,12 +276,12 @@ github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmx
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.2.0 h1:yBBLMasHA70Ujd35OpL/OjJOWWVNXcJGbars0GinGRI=
|
||||
github.com/github/go-spdx/v2 v2.2.0/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
|
||||
github.com/gkampitakis/ciinfo v0.2.5 h1:K0mac90lGguc1conc46l0YEsB7/nioWCqSnJp/6z8Eo=
|
||||
github.com/gkampitakis/ciinfo v0.2.5/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
|
||||
github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
|
||||
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
|
||||
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
|
||||
github.com/gkampitakis/go-snaps v0.4.11 h1:7qKaozbTQEvHeG0bt6osdjdTDTnWYdIrLx43a7DEDu4=
|
||||
github.com/gkampitakis/go-snaps v0.4.11/go.mod h1:N4TpqxI4CqKUfHzDFqrqZ5UP0I0ESz2g2NMslh7MiJw=
|
||||
github.com/gkampitakis/go-snaps v0.4.12 h1:YeMgKOm0XW3f/Pt2rYpUlpyF8nG6lYGe9oXFJw5LdME=
|
||||
github.com/gkampitakis/go-snaps v0.4.12/go.mod h1:PpnF1KPXQAHBdb/DHoi/1VmlwE+ZkVHzl+QHmgzMSz8=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
@@ -514,6 +515,8 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -713,8 +716,8 @@ github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -754,8 +757,8 @@ github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1ump
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
||||
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
@@ -847,8 +850,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -937,8 +940,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1056,16 +1059,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -1078,8 +1081,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -3,5 +3,5 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "12.0.0"
|
||||
JSONSchemaVersion = "12.0.1"
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,7 +63,7 @@ func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Locati
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
defer internal.CloseAndLogError(contentReader, location.AccessPath)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
|
||||
@@ -81,7 +81,7 @@ func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Locati
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
defer internal.CloseAndLogError(contentReader, location.AccessPath)
|
||||
|
||||
digests, err := intFile.NewDigestsFromFile(contentReader, i.hashes)
|
||||
if err != nil {
|
||||
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
|
||||
func Test_allRegularFiles(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func() file.Resolver
|
||||
wantRealPaths *strset.Set
|
||||
wantVirtualPaths *strset.Set
|
||||
name string
|
||||
setup func() file.Resolver
|
||||
wantRealPaths *strset.Set
|
||||
wantAccessPaths *strset.Set
|
||||
}{
|
||||
{
|
||||
name: "image",
|
||||
@@ -35,8 +35,8 @@ func Test_allRegularFiles(t *testing.T) {
|
||||
|
||||
return r
|
||||
},
|
||||
wantRealPaths: strset.New("/file-1.txt"),
|
||||
wantVirtualPaths: strset.New("/file-1.txt", "/symlink-1", "/hardlink-1"),
|
||||
wantRealPaths: strset.New("/file-1.txt"),
|
||||
wantAccessPaths: strset.New("/file-1.txt", "/symlink-1", "/hardlink-1"),
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
@@ -47,8 +47,8 @@ func Test_allRegularFiles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
return r
|
||||
},
|
||||
wantRealPaths: strset.New("file1.txt", "nested/file2.txt"),
|
||||
wantVirtualPaths: strset.New("file1.txt", "nested/file2.txt", "nested/linked-file1.txt"),
|
||||
wantRealPaths: strset.New("file1.txt", "nested/file2.txt"),
|
||||
wantAccessPaths: strset.New("file1.txt", "nested/file2.txt", "nested/linked-file1.txt"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -59,8 +59,8 @@ func Test_allRegularFiles(t *testing.T) {
|
||||
virtualLocations := strset.New()
|
||||
for _, l := range locations {
|
||||
realLocations.Add(l.RealPath)
|
||||
if l.VirtualPath != "" {
|
||||
virtualLocations.Add(l.VirtualPath)
|
||||
if l.AccessPath != "" {
|
||||
virtualLocations.Add(l.AccessPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,10 +68,10 @@ func Test_allRegularFiles(t *testing.T) {
|
||||
realLocations.Remove("/hardlink-1")
|
||||
virtualLocations.Remove("/hardlink-1")
|
||||
tt.wantRealPaths.Remove("/hardlink-1")
|
||||
tt.wantVirtualPaths.Remove("/hardlink-1")
|
||||
tt.wantAccessPaths.Remove("/hardlink-1")
|
||||
|
||||
assert.ElementsMatch(t, tt.wantRealPaths.List(), realLocations.List(), "real paths differ: "+cmp.Diff(tt.wantRealPaths.List(), realLocations.List()))
|
||||
assert.ElementsMatch(t, tt.wantVirtualPaths.List(), virtualLocations.List(), "virtual paths differ: "+cmp.Diff(tt.wantVirtualPaths.List(), virtualLocations.List()))
|
||||
assert.ElementsMatch(t, tt.wantAccessPaths.List(), virtualLocations.List(), "virtual paths differ: "+cmp.Diff(tt.wantAccessPaths.List(), virtualLocations.List()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ func extractValue(resolver file.Resolver, location file.Location, start, length
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
|
||||
}
|
||||
defer internal.CloseAndLogError(readCloser, location.VirtualPath)
|
||||
defer internal.CloseAndLogError(readCloser, location.AccessPath)
|
||||
|
||||
n, err := io.CopyN(io.Discard, readCloser, start)
|
||||
if err != nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ func catalogLocationByLine(resolver file.Resolver, location file.Location, patte
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
|
||||
}
|
||||
defer internal.CloseAndLogError(readCloser, location.VirtualPath)
|
||||
defer internal.CloseAndLogError(readCloser, location.AccessPath)
|
||||
|
||||
var scanner = bufio.NewReader(readCloser)
|
||||
var position int64
|
||||
@@ -65,7 +65,7 @@ func searchForSecretsWithinLine(resolver file.Resolver, location file.Location,
|
||||
if secret != nil {
|
||||
secrets = append(secrets, *secret)
|
||||
}
|
||||
internal.CloseAndLogError(reader, location.VirtualPath)
|
||||
internal.CloseAndLogError(reader, location.AccessPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-23
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// Location represents a path relative to a particular filesystem resolved to a specific file.Reference. This struct is used as a key
|
||||
// in content fetching to uniquely identify a file relative to a request (the VirtualPath).
|
||||
// in content fetching to uniquely identify a file relative to a request (the AccessPath).
|
||||
type Location struct {
|
||||
LocationData `cyclonedx:""`
|
||||
LocationMetadata `cyclonedx:""`
|
||||
@@ -20,8 +20,8 @@ type LocationData struct {
|
||||
Coordinates `cyclonedx:""` // Empty string here means there is no intermediate property name, e.g. syft:locations:0:path without "coordinates"
|
||||
// note: it is IMPORTANT to ignore anything but the coordinates for a Location when considering the ID (hash value)
|
||||
// since the coordinates are the minimally correct ID for a location (symlinks should not come into play)
|
||||
VirtualPath string `hash:"ignore" json:"-"` // The path to the file which may or may not have hardlinks / symlinks
|
||||
ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location.
|
||||
AccessPath string `hash:"ignore" json:"accessPath"` // The path to the file which may or may not have hardlinks / symlinks
|
||||
ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location.
|
||||
}
|
||||
|
||||
func (l LocationData) Reference() file.Reference {
|
||||
@@ -68,7 +68,7 @@ func NewLocation(realPath string) Location {
|
||||
Coordinates: Coordinates{
|
||||
RealPath: realPath,
|
||||
},
|
||||
VirtualPath: realPath,
|
||||
AccessPath: realPath,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -77,13 +77,13 @@ func NewLocation(realPath string) Location {
|
||||
}
|
||||
|
||||
// NewVirtualLocation creates a new location for a path accessed by a virtual path (a path with a symlink or hardlink somewhere in the path)
|
||||
func NewVirtualLocation(realPath, virtualPath string) Location {
|
||||
func NewVirtualLocation(realPath, accessPath string) Location {
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: Coordinates{
|
||||
RealPath: realPath,
|
||||
},
|
||||
VirtualPath: virtualPath,
|
||||
AccessPath: accessPath,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -95,7 +95,7 @@ func NewLocationFromCoordinates(coordinates Coordinates) Location {
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: coordinates,
|
||||
VirtualPath: coordinates.RealPath,
|
||||
AccessPath: coordinates.RealPath,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -103,11 +103,11 @@ func NewLocationFromCoordinates(coordinates Coordinates) Location {
|
||||
}
|
||||
|
||||
// NewVirtualLocationFromCoordinates creates a new location for the given Coordinates via a virtual path.
|
||||
func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath string) Location {
|
||||
func NewVirtualLocationFromCoordinates(coordinates Coordinates, accessPath string) Location {
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: coordinates,
|
||||
VirtualPath: virtualPath,
|
||||
AccessPath: accessPath,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -115,7 +115,7 @@ func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath stri
|
||||
}
|
||||
|
||||
// NewLocationFromImage creates a new Location representing the given path (extracted from the Reference) relative to the given image.
|
||||
func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location {
|
||||
func NewLocationFromImage(accessPath string, ref file.Reference, img *image.Image) Location {
|
||||
layer := img.FileCatalog.Layer(ref)
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
@@ -123,8 +123,8 @@ func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Ima
|
||||
RealPath: string(ref.RealPath),
|
||||
FileSystemID: layer.Metadata.Digest,
|
||||
},
|
||||
VirtualPath: virtualPath,
|
||||
ref: ref,
|
||||
AccessPath: accessPath,
|
||||
ref: ref,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -139,8 +139,8 @@ func NewLocationFromDirectory(responsePath string, ref file.Reference) Location
|
||||
Coordinates: Coordinates{
|
||||
RealPath: responsePath,
|
||||
},
|
||||
VirtualPath: responsePath,
|
||||
ref: ref,
|
||||
AccessPath: responsePath,
|
||||
ref: ref,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -149,14 +149,14 @@ func NewLocationFromDirectory(responsePath string, ref file.Reference) Location
|
||||
}
|
||||
|
||||
// NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory with a separate virtual access path.
|
||||
func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, ref file.Reference) Location {
|
||||
func NewVirtualLocationFromDirectory(responsePath, responseAccessPath string, ref file.Reference) Location {
|
||||
return Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: Coordinates{
|
||||
RealPath: responsePath,
|
||||
},
|
||||
VirtualPath: virtualResponsePath,
|
||||
ref: ref,
|
||||
AccessPath: responseAccessPath,
|
||||
ref: ref,
|
||||
},
|
||||
LocationMetadata: LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
@@ -164,9 +164,9 @@ func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, r
|
||||
}
|
||||
}
|
||||
|
||||
func (l Location) AccessPath() string {
|
||||
if l.VirtualPath != "" {
|
||||
return l.VirtualPath
|
||||
func (l Location) Path() string {
|
||||
if l.AccessPath != "" {
|
||||
return l.AccessPath
|
||||
}
|
||||
return l.RealPath
|
||||
}
|
||||
@@ -179,8 +179,8 @@ func (l Location) String() string {
|
||||
|
||||
str += fmt.Sprintf("RealPath=%q", l.RealPath)
|
||||
|
||||
if l.VirtualPath != "" {
|
||||
str += fmt.Sprintf(" VirtualPath=%q", l.VirtualPath)
|
||||
if l.AccessPath != "" && l.AccessPath != l.RealPath {
|
||||
str += fmt.Sprintf(" AccessPath=%q", l.AccessPath)
|
||||
}
|
||||
|
||||
if l.FileSystemID != "" {
|
||||
@@ -191,6 +191,6 @@ func (l Location) String() string {
|
||||
|
||||
func (l Location) Equals(other Location) bool {
|
||||
return l.RealPath == other.RealPath &&
|
||||
l.VirtualPath == other.VirtualPath &&
|
||||
l.AccessPath == other.AccessPath &&
|
||||
l.FileSystemID == other.FileSystemID
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestLocationSet(t *testing.T) {
|
||||
RealPath: "/etc/hosts",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/var/etc/hosts",
|
||||
AccessPath: "/var/etc/hosts",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestLocationSet(t *testing.T) {
|
||||
RealPath: "/etc/hosts",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/home/wagoodman/hosts",
|
||||
AccessPath: "/home/wagoodman/hosts",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestLocationSet(t *testing.T) {
|
||||
RealPath: "/bin",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/usr/bin",
|
||||
AccessPath: "/usr/bin",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func TestLocationSet(t *testing.T) {
|
||||
RealPath: "/bin",
|
||||
FileSystemID: "b",
|
||||
},
|
||||
VirtualPath: "/usr/bin",
|
||||
AccessPath: "/usr/bin",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestLocationSet_Hash(t *testing.T) {
|
||||
RealPath: "/etc/hosts",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/var/etc/hosts",
|
||||
AccessPath: "/var/etc/hosts",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ func TestLocationSet_Hash(t *testing.T) {
|
||||
RealPath: "/bin",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/usr/bin",
|
||||
AccessPath: "/usr/bin",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func TestLocationSet_Hash(t *testing.T) {
|
||||
RealPath: "/bin",
|
||||
FileSystemID: "b",
|
||||
},
|
||||
VirtualPath: "/usr/bin",
|
||||
AccessPath: "/usr/bin",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestLocation_ID(t *testing.T) {
|
||||
l := Location{
|
||||
LocationData: LocationData{
|
||||
Coordinates: test.coordinates,
|
||||
VirtualPath: test.virtualPath,
|
||||
AccessPath: test.virtualPath,
|
||||
ref: test.ref,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ func (l Locations) Len() int {
|
||||
|
||||
func (l Locations) Less(i, j int) bool {
|
||||
if l[i].RealPath == l[j].RealPath {
|
||||
if l[i].VirtualPath == l[j].VirtualPath {
|
||||
if l[i].AccessPath == l[j].AccessPath {
|
||||
return l[i].FileSystemID < l[j].FileSystemID
|
||||
}
|
||||
return l[i].VirtualPath < l[j].VirtualPath
|
||||
return l[i].AccessPath < l[j].AccessPath
|
||||
}
|
||||
return l[i].RealPath < l[j].RealPath
|
||||
}
|
||||
|
||||
@@ -213,6 +213,12 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
|
||||
|
||||
switch c.Type {
|
||||
case cyclonedx.ComponentTypeContainer:
|
||||
var labels map[string]string
|
||||
|
||||
if meta.Properties != nil {
|
||||
labels = decodeProperties(*meta.Properties, "syft:image:labels:")
|
||||
}
|
||||
|
||||
return source.Description{
|
||||
ID: "",
|
||||
// TODO: can we decode alias name-version somehow? (it isn't be encoded in the first place yet)
|
||||
@@ -221,6 +227,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
|
||||
UserInput: c.Name,
|
||||
ID: c.BOMRef,
|
||||
ManifestDigest: c.Version,
|
||||
Labels: labels,
|
||||
},
|
||||
}
|
||||
case cyclonedx.ComponentTypeFile:
|
||||
|
||||
@@ -97,7 +97,6 @@ func Test_decode(t *testing.T) {
|
||||
CPE: "cpe:2.3:*:another:package:2:*:*:*:*:*:*:*",
|
||||
PackageURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.14.2",
|
||||
Properties: &[]cyclonedx.Property{
|
||||
|
||||
{
|
||||
Name: "foundBy",
|
||||
Value: "apkdb-cataloger",
|
||||
|
||||
@@ -121,7 +121,8 @@ func toBomDescriptor(name, version string, srcMetadata source.Description) *cycl
|
||||
Version: version,
|
||||
},
|
||||
},
|
||||
Component: toBomDescriptorComponent(srcMetadata),
|
||||
Properties: toBomProperties(srcMetadata),
|
||||
Component: toBomDescriptorComponent(srcMetadata),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +191,15 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
||||
return result
|
||||
}
|
||||
|
||||
func toBomProperties(srcMetadata source.Description) *[]cyclonedx.Property {
|
||||
metadata, ok := srcMetadata.Metadata.(source.StereoscopeImageSourceMetadata)
|
||||
if ok {
|
||||
props := encodeProperties(metadata.Labels, "syft:image:labels")
|
||||
return &props
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Component {
|
||||
name := srcMetadata.Name
|
||||
version := srcMetadata.Version
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_formatCPE(t *testing.T) {
|
||||
@@ -138,3 +140,95 @@ func Test_relationships(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toBomDescriptor(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
version string
|
||||
srcMetadata source.Description
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *cyclonedx.Metadata
|
||||
}{
|
||||
{
|
||||
name: "with image labels source metadata",
|
||||
args: args{
|
||||
name: "test-image",
|
||||
version: "1.0.0",
|
||||
srcMetadata: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Labels: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &cyclonedx.Metadata{
|
||||
Timestamp: "",
|
||||
Lifecycles: nil,
|
||||
Tools: &[]cyclonedx.Tool{
|
||||
{
|
||||
Vendor: "anchore",
|
||||
Name: "test-image",
|
||||
Version: "1.0.0",
|
||||
Hashes: nil,
|
||||
ExternalReferences: nil,
|
||||
},
|
||||
},
|
||||
Authors: nil,
|
||||
Component: &cyclonedx.Component{
|
||||
BOMRef: "",
|
||||
MIMEType: "",
|
||||
Type: "container",
|
||||
Supplier: nil,
|
||||
Author: "",
|
||||
Publisher: "",
|
||||
Group: "",
|
||||
Name: "",
|
||||
Version: "",
|
||||
Description: "",
|
||||
Scope: "",
|
||||
Hashes: nil,
|
||||
Licenses: nil,
|
||||
Copyright: "",
|
||||
CPE: "",
|
||||
PackageURL: "",
|
||||
SWID: nil,
|
||||
Modified: nil,
|
||||
Pedigree: nil,
|
||||
ExternalReferences: nil,
|
||||
Properties: nil,
|
||||
Components: nil,
|
||||
Evidence: nil,
|
||||
ReleaseNotes: nil,
|
||||
},
|
||||
Manufacture: nil,
|
||||
Supplier: nil,
|
||||
Licenses: nil,
|
||||
Properties: &[]cyclonedx.Property{
|
||||
{
|
||||
Name: "syft:image:labels:key1",
|
||||
Value: "value1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
subject := toBomDescriptor(tt.args.name, tt.args.version, tt.args.srcMetadata)
|
||||
|
||||
require.NotEmpty(t, subject.Component.BOMRef)
|
||||
subject.Timestamp = "" // not under test
|
||||
|
||||
require.NotNil(t, subject.Component)
|
||||
require.NotEmpty(t, subject.Component.BOMRef)
|
||||
subject.Component.BOMRef = "" // not under test
|
||||
|
||||
if d := cmp.Diff(tt.want, subject); d != "" {
|
||||
t.Errorf("toBomDescriptor() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/syft/format/common"
|
||||
@@ -19,3 +21,14 @@ func encodeProperties(obj interface{}, prefix string) (out []cyclonedx.Property)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeProperties(properties []cyclonedx.Property, prefix string) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
for _, property := range properties {
|
||||
if strings.HasPrefix(property.Name, prefix) {
|
||||
labelName := strings.TrimPrefix(property.Name, prefix)
|
||||
labels[labelName] = property.Value
|
||||
}
|
||||
}
|
||||
return labels
|
||||
}
|
||||
|
||||
@@ -636,7 +636,7 @@ func Test_directPackageFiles(t *testing.T) {
|
||||
RealPath: "some-file",
|
||||
FileSystemID: "",
|
||||
},
|
||||
VirtualPath: "some-file",
|
||||
AccessPath: "some-file",
|
||||
},
|
||||
LocationMetadata: file.LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
@@ -75,7 +74,7 @@ func (d decoder) Identify(reader io.ReadSeeker) (sbom.FormatID, string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
id, version := getFormatInfo(doc.JSONSchema, doc.BOMFormat, doc.SpecVersion)
|
||||
id, version := getFormatInfo(doc.BOMFormat, doc.SpecVersion)
|
||||
if version == "" || id != ID {
|
||||
// not a cyclonedx json document that we support
|
||||
return "", ""
|
||||
@@ -84,18 +83,13 @@ func (d decoder) Identify(reader io.ReadSeeker) (sbom.FormatID, string) {
|
||||
return id, version
|
||||
}
|
||||
|
||||
func getFormatInfo(schemaURI, bomFormat string, specVersion any) (sbom.FormatID, string) {
|
||||
if !strings.Contains(schemaURI, "cyclonedx.org/schema/bom") {
|
||||
// not a cyclonedx json document
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func getFormatInfo(bomFormat string, specVersion any) (sbom.FormatID, string) {
|
||||
if bomFormat != "CycloneDX" {
|
||||
// not a cyclonedx json document
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// by this point this looks to be valid cyclonedx json, but we need to know the version
|
||||
// by this point, it looks to be cyclonedx json, but we need to know the version
|
||||
|
||||
var (
|
||||
version string
|
||||
|
||||
@@ -103,6 +103,21 @@ func TestDecoder_Identify(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
cases = append(cases, []testCase{
|
||||
{
|
||||
name: "no-schema-1.4",
|
||||
file: "test-fixtures/identify/micronaut-1.4.json",
|
||||
id: ID,
|
||||
version: "1.4",
|
||||
},
|
||||
{
|
||||
name: "no-schema-1.5",
|
||||
file: "test-fixtures/identify/micronaut-1.5.json",
|
||||
id: ID,
|
||||
version: "1.5",
|
||||
},
|
||||
}...)
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
reader, err := os.Open(test.file)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"version": 1,
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.netty",
|
||||
"name": "netty-codec-http2",
|
||||
"version": "4.1.73.Final",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:codec:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty-codec-http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.5",
|
||||
"version": 1,
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.netty",
|
||||
"name": "netty-codec-http2",
|
||||
"version": "4.1.73.Final",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:codec:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty-codec-http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"serialNumber": "urn:uuid:f4-f715-3d85-9629-336fdd3790ad"
|
||||
}
|
||||
@@ -111,8 +111,8 @@ func toPath(s source.Description, p pkg.Package) string {
|
||||
if len(locations) > 0 {
|
||||
location := locations[0]
|
||||
packagePath := location.RealPath
|
||||
if location.VirtualPath != "" {
|
||||
packagePath = location.VirtualPath
|
||||
if location.AccessPath != "" {
|
||||
packagePath = location.AccessPath
|
||||
}
|
||||
packagePath = strings.TrimPrefix(packagePath, "/")
|
||||
switch metadata := s.Metadata.(type) {
|
||||
|
||||
@@ -123,7 +123,7 @@ FileCopyrightText: NOASSERTION
|
||||
f = file.Location{
|
||||
LocationData: file.LocationData{
|
||||
Coordinates: c,
|
||||
VirtualPath: "",
|
||||
AccessPath: "",
|
||||
},
|
||||
LocationMetadata: file.LocationMetadata{},
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func Test_EncodeDecodeCycle(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, d := range deep.Equal(p, actualPackages[idx]) {
|
||||
if strings.Contains(d, ".VirtualPath: ") {
|
||||
if strings.Contains(d, ".AccessPath: ") {
|
||||
// location.Virtual path is not exposed in the json output
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
file.NewLocation("/a/place").Coordinates: {
|
||||
file.NewVirtualLocation("/a/place", "/a/symlink/to/place").Coordinates: {
|
||||
FileInfo: stereoFile.ManualInfo{
|
||||
NameValue: "/a/place",
|
||||
ModeValue: 0775,
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
"path": "/some/path/pkg1",
|
||||
"accessPath": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -48,7 +49,8 @@
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/some/path/pkg1"
|
||||
"path": "/some/path/pkg1",
|
||||
"accessPath": "/some/path/pkg1"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"foundBy": "the-cataloger-1",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/a/place/a"
|
||||
"path": "/a/place/a",
|
||||
"accessPath": "/a/place/a"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -43,7 +44,8 @@
|
||||
"foundBy": "the-cataloger-2",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/b/place/b"
|
||||
"path": "/b/place/b",
|
||||
"accessPath": "/b/place/b"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:100d5a55f9032faead28b7427fa3e650e4f0158f86ea89d06e1489df00cb8c6f"
|
||||
"layerID": "sha256:100d5a55f9032faead28b7427fa3e650e4f0158f86ea89d06e1489df00cb8c6f",
|
||||
"accessPath": "/somefile-1.txt"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
@@ -45,7 +46,8 @@
|
||||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:000fb9200890d3a19138478b20023023c0dce1c54352007c2863716780f049eb"
|
||||
"layerID": "sha256:000fb9200890d3a19138478b20023023c0dce1c54352007c2863716780f049eb",
|
||||
"accessPath": "/somefile-2.txt"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
|
||||
@@ -20,6 +20,18 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// MetadataType infers the metadata type value based on the pkg.Metadata payload.
|
||||
func MetadataType(metadata interface{}) string {
|
||||
return metadataType(metadata, false)
|
||||
}
|
||||
|
||||
func metadataType(metadata interface{}, legacy bool) string {
|
||||
if legacy {
|
||||
return packagemetadata.JSONLegacyName(metadata)
|
||||
}
|
||||
return packagemetadata.JSONName(metadata)
|
||||
}
|
||||
|
||||
// ToFormatModel transforms the sbom import a format-specific model.
|
||||
func ToFormatModel(s sbom.SBOM, cfg EncoderConfig) model.Document {
|
||||
return model.Document{
|
||||
@@ -247,13 +259,6 @@ func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
|
||||
licenses = toLicenseModel(p.Licenses.ToSlice())
|
||||
}
|
||||
|
||||
var ty string
|
||||
if cfg.Legacy {
|
||||
ty = packagemetadata.JSONLegacyName(p.Metadata)
|
||||
} else {
|
||||
ty = packagemetadata.JSONName(p.Metadata)
|
||||
}
|
||||
|
||||
return model.Package{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: string(p.ID()),
|
||||
@@ -268,7 +273,7 @@ func toPackageModel(p pkg.Package, cfg EncoderConfig) model.Package {
|
||||
PURL: p.PURL,
|
||||
},
|
||||
PackageCustomData: model.PackageCustomData{
|
||||
MetadataType: ty,
|
||||
MetadataType: metadataType(p.Metadata, cfg.Legacy),
|
||||
Metadata: p.Metadata,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -193,10 +193,10 @@ func (r *ContainerImageAllLayers) FileContentsByLocation(location file.Location)
|
||||
switch entry.Metadata.Type {
|
||||
case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
|
||||
// the location we are searching may be a symlink, we should always work with the resolved file
|
||||
newLocation := r.RelativeFileByPath(location, location.VirtualPath)
|
||||
newLocation := r.RelativeFileByPath(location, location.AccessPath)
|
||||
if newLocation == nil {
|
||||
// this is a dead link
|
||||
return nil, fmt.Errorf("no contents for location=%q", location.VirtualPath)
|
||||
return nil, fmt.Errorf("no contents for location=%q", location.AccessPath)
|
||||
}
|
||||
location = *newLocation
|
||||
case stereoscopeFile.TypeDirectory:
|
||||
|
||||
@@ -110,7 +110,7 @@ func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
// we should be resolving symlinks and preserving this information as a VirtualPath to the real file
|
||||
// we should be resolving symlinks and preserving this information as a AccessPath to the real file
|
||||
ref, err := r.searchContext.SearchByPath(userStrPath, filetree.FollowBasenameLinks)
|
||||
if err != nil {
|
||||
log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err)
|
||||
|
||||
@@ -65,13 +65,13 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
cwd string
|
||||
root string
|
||||
base string
|
||||
input string
|
||||
expectedRealPath string
|
||||
expectedVirtualPath string // note: if empty it will be assumed to match the expectedRealPath
|
||||
name string
|
||||
cwd string
|
||||
root string
|
||||
base string
|
||||
input string
|
||||
expectedRealPath string
|
||||
expectedAccessPath string // note: if empty it will be assumed to match the expectedRealPath
|
||||
}{
|
||||
{
|
||||
name: "relative root, relative request, direct",
|
||||
@@ -139,7 +139,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "path/to/the/file.txt",
|
||||
expectedVirtualPath: "path/to/the/file.txt",
|
||||
expectedAccessPath: "path/to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, direct, cwd within symlink root",
|
||||
@@ -159,7 +159,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "path/to/the/file.txt",
|
||||
expectedVirtualPath: "path/to/the/file.txt",
|
||||
expectedAccessPath: "path/to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, direct, cwd within symlink root",
|
||||
@@ -180,7 +180,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative nested request, direct, cwd within symlink root",
|
||||
@@ -200,7 +200,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs nested request, direct, cwd within symlink root",
|
||||
@@ -221,7 +221,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "path/to/the/file.txt",
|
||||
expectedVirtualPath: "path/to/the/file.txt",
|
||||
expectedAccessPath: "path/to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, direct, cwd within (double) symlink root",
|
||||
@@ -241,7 +241,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "path/to/the/file.txt",
|
||||
expectedVirtualPath: "path/to/the/file.txt",
|
||||
expectedAccessPath: "path/to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, direct, cwd within (double) symlink root",
|
||||
@@ -262,7 +262,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative nested request, direct, cwd within (double) symlink root",
|
||||
@@ -282,7 +282,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs nested request, direct, cwd within (double) symlink root",
|
||||
@@ -303,7 +303,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative nested request, direct, cwd deep within (double) symlink root",
|
||||
@@ -323,7 +323,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
// outside the root.
|
||||
expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"),
|
||||
//expectedRealPath: "to/the/file.txt",
|
||||
expectedVirtualPath: "to/the/file.txt",
|
||||
expectedAccessPath: "to/the/file.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs nested request, direct, cwd deep within (double) symlink root",
|
||||
@@ -334,163 +334,163 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
},
|
||||
// link to outside of root cases...
|
||||
{
|
||||
name: "relative root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, relative request, relative indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, relative request, relative indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, abs request, relative indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, abs request, relative indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
// link to outside of root cases... cwd within symlink root
|
||||
{
|
||||
name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: "path",
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: "path",
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: "path",
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: "path",
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
cwd: relativeViaDoubleLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/rel-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.expectedVirtualPath == "" {
|
||||
c.expectedVirtualPath = c.expectedRealPath
|
||||
if c.expectedAccessPath == "" {
|
||||
c.expectedAccessPath = c.expectedRealPath
|
||||
}
|
||||
|
||||
// we need to mimic a shell, otherwise we won't get a path within a symlink
|
||||
@@ -515,7 +515,7 @@ func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
}
|
||||
require.Len(t, refs, 1)
|
||||
assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different")
|
||||
assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different")
|
||||
assert.Equal(t, c.expectedAccessPath, refs[0].AccessPath, "virtual path different")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -961,14 +961,14 @@ func Test_IndexingNestedSymLinks(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locations, 1)
|
||||
assert.Equal(t, "readme", locations[0].RealPath)
|
||||
assert.Equal(t, "link_to_new_readme", locations[0].VirtualPath)
|
||||
assert.Equal(t, "link_to_new_readme", locations[0].AccessPath)
|
||||
|
||||
// check that we can access the same file via 2 symlinks
|
||||
locations, err = resolver.FilesByPath("./link_to_link_to_new_readme")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locations, 1)
|
||||
assert.Equal(t, "readme", locations[0].RealPath)
|
||||
assert.Equal(t, "link_to_link_to_new_readme", locations[0].VirtualPath)
|
||||
assert.Equal(t, "link_to_link_to_new_readme", locations[0].AccessPath)
|
||||
|
||||
// check that we can access the same file via 2 symlinks
|
||||
locations, err = resolver.FilesByGlob("**/link_*")
|
||||
@@ -976,7 +976,7 @@ func Test_IndexingNestedSymLinks(t *testing.T) {
|
||||
require.Len(t, locations, 1) // you would think this is 2, however, they point to the same file, and glob only returns unique files
|
||||
|
||||
// returned locations can be in any order
|
||||
expectedVirtualPaths := []string{
|
||||
expectedAccessPaths := []string{
|
||||
"link_to_link_to_new_readme",
|
||||
//"link_to_new_readme", // we filter out this one because the first symlink resolves to the same file
|
||||
}
|
||||
@@ -986,13 +986,13 @@ func Test_IndexingNestedSymLinks(t *testing.T) {
|
||||
}
|
||||
|
||||
actualRealPaths := strset.New()
|
||||
actualVirtualPaths := strset.New()
|
||||
actualAccessPaths := strset.New()
|
||||
for _, a := range locations {
|
||||
actualVirtualPaths.Add(a.VirtualPath)
|
||||
actualAccessPaths.Add(a.AccessPath)
|
||||
actualRealPaths.Add(a.RealPath)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, expectedVirtualPaths, actualVirtualPaths.List())
|
||||
assert.ElementsMatch(t, expectedAccessPaths, actualAccessPaths.List())
|
||||
assert.ElementsMatch(t, expectedRealPaths, actualRealPaths.List())
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ func (r *excluding) AllLocations() <-chan file.Location {
|
||||
}
|
||||
|
||||
func locationMatches(location *file.Location, exclusionFn excludeFn) bool {
|
||||
return exclusionFn(location.RealPath) || exclusionFn(location.VirtualPath)
|
||||
return exclusionFn(location.RealPath) || exclusionFn(location.AccessPath)
|
||||
}
|
||||
|
||||
func filterLocations(locations []file.Location, err error, exclusionFn excludeFn) ([]file.Location, error) {
|
||||
|
||||
@@ -165,8 +165,8 @@ nextPath:
|
||||
for i := range out {
|
||||
existing := &out[i]
|
||||
if existing.RealPath == l.RealPath {
|
||||
if l.VirtualPath == "" {
|
||||
existing.VirtualPath = ""
|
||||
if l.AccessPath == "" {
|
||||
existing.AccessPath = ""
|
||||
}
|
||||
continue nextPath
|
||||
}
|
||||
|
||||
@@ -90,13 +90,13 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
t.Cleanup(cleanup)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
cwd string
|
||||
root string
|
||||
base string
|
||||
input string
|
||||
expectedRealPath string
|
||||
expectedVirtualPath string // if empty, the virtual path should be the same as the real path
|
||||
name string
|
||||
cwd string
|
||||
root string
|
||||
base string
|
||||
input string
|
||||
expectedRealPath string
|
||||
expectedAccessPath string // if empty, the virtual path should be the same as the real path
|
||||
}{
|
||||
{
|
||||
name: "relative root, relative request, direct",
|
||||
@@ -319,32 +319,32 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
},
|
||||
// link to outside of root cases...
|
||||
{
|
||||
name: "relative root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, relative request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(relative, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, abs request, abs indirect (outside of root)",
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root)",
|
||||
@@ -352,8 +352,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root)",
|
||||
@@ -361,8 +361,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root)",
|
||||
@@ -370,8 +370,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root)",
|
||||
@@ -379,41 +379,41 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
// link to outside of root cases... cwd within symlink root
|
||||
{
|
||||
name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: "path",
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedVirtualPath: "to/the/abs-outside.txt",
|
||||
name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root",
|
||||
cwd: relativeViaLink,
|
||||
root: filepath.Join(absolute, "path"),
|
||||
input: "/to/the/abs-outside.txt",
|
||||
expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
expectedAccessPath: "to/the/abs-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
@@ -422,8 +422,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root",
|
||||
@@ -432,8 +432,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
@@ -442,8 +442,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root",
|
||||
@@ -452,8 +452,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
@@ -462,8 +462,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
@@ -472,8 +472,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
@@ -482,8 +482,8 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
{
|
||||
name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root",
|
||||
@@ -492,14 +492,14 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
input: "/to/the/rel-outside.txt",
|
||||
//expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"),
|
||||
// TODO: the real path is not correct
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedVirtualPath: "to/the/rel-outside.txt",
|
||||
expectedRealPath: "../somewhere/outside.txt",
|
||||
expectedAccessPath: "to/the/rel-outside.txt",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.expectedVirtualPath == "" {
|
||||
c.expectedVirtualPath = c.expectedRealPath
|
||||
if c.expectedAccessPath == "" {
|
||||
c.expectedAccessPath = c.expectedRealPath
|
||||
}
|
||||
|
||||
// we need to mimic a shell, otherwise we won't get a path within a symlink
|
||||
@@ -523,7 +523,7 @@ func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) {
|
||||
}
|
||||
require.Len(t, refs, 1)
|
||||
assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different")
|
||||
assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different")
|
||||
assert.Equal(t, c.expectedAccessPath, refs[0].AccessPath, "virtual path different")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -847,14 +847,14 @@ func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locations, 1)
|
||||
assert.Equal(t, "readme", locations[0].RealPath)
|
||||
assert.Equal(t, "link_to_new_readme", locations[0].VirtualPath)
|
||||
assert.Equal(t, "link_to_new_readme", locations[0].AccessPath)
|
||||
|
||||
// check that we can access the same file via 2 symlinks
|
||||
locations, err = resolver.FilesByPath("./link_to_link_to_new_readme")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locations, 1)
|
||||
assert.Equal(t, "readme", locations[0].RealPath)
|
||||
assert.Equal(t, "link_to_link_to_new_readme", locations[0].VirtualPath)
|
||||
assert.Equal(t, "link_to_link_to_new_readme", locations[0].AccessPath)
|
||||
|
||||
// check that we can access the same file via 2 symlinks
|
||||
locations, err = resolver.FilesByGlob("**/link_*")
|
||||
@@ -862,7 +862,7 @@ func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) {
|
||||
require.Len(t, locations, 1) // you would think this is 2, however, they point to the same file, and glob only returns unique files
|
||||
|
||||
// returned locations can be in any order
|
||||
expectedVirtualPaths := []string{
|
||||
expectedAccessPaths := []string{
|
||||
"link_to_link_to_new_readme",
|
||||
//"link_to_new_readme", // we filter out this one because the first symlink resolves to the same file
|
||||
}
|
||||
@@ -872,13 +872,13 @@ func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) {
|
||||
}
|
||||
|
||||
actualRealPaths := strset.New()
|
||||
actualVirtualPaths := strset.New()
|
||||
actualAccessPaths := strset.New()
|
||||
for _, a := range locations {
|
||||
actualVirtualPaths.Add(a.VirtualPath)
|
||||
actualAccessPaths.Add(a.AccessPath)
|
||||
actualRealPaths.Add(a.RealPath)
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, expectedVirtualPaths, actualVirtualPaths.List())
|
||||
assert.ElementsMatch(t, expectedAccessPaths, actualAccessPaths.List())
|
||||
assert.ElementsMatch(t, expectedRealPaths, actualRealPaths.List())
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ func IdentifyRelease(resolver file.Resolver) *Release {
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(contentReader)
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
internal.CloseAndLogError(contentReader, location.AccessPath)
|
||||
if err != nil {
|
||||
logger.WithFields("error", err, "path", location.RealPath).Trace("unable to read contents")
|
||||
continue
|
||||
|
||||
+6
-6
@@ -130,9 +130,9 @@ func (c *Collection) addPathsToIndex(p Package) {
|
||||
c.addPathToIndex(p.id, l.RealPath)
|
||||
observedPaths.Add(l.RealPath)
|
||||
}
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Has(l.VirtualPath) {
|
||||
c.addPathToIndex(p.id, l.VirtualPath)
|
||||
observedPaths.Add(l.VirtualPath)
|
||||
if l.AccessPath != "" && l.RealPath != l.AccessPath && !observedPaths.Has(l.AccessPath) {
|
||||
c.addPathToIndex(p.id, l.AccessPath)
|
||||
observedPaths.Add(l.AccessPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,9 +179,9 @@ func (c *Collection) deletePathsFromIndex(p Package) {
|
||||
c.deletePathFromIndex(p.id, l.RealPath)
|
||||
observedPaths.Add(l.RealPath)
|
||||
}
|
||||
if l.VirtualPath != "" && l.RealPath != l.VirtualPath && !observedPaths.Has(l.VirtualPath) {
|
||||
c.deletePathFromIndex(p.id, l.VirtualPath)
|
||||
observedPaths.Add(l.VirtualPath)
|
||||
if l.AccessPath != "" && l.RealPath != l.AccessPath && !observedPaths.Has(l.AccessPath) {
|
||||
c.deletePathFromIndex(p.id, l.AccessPath)
|
||||
observedPaths.Add(l.AccessPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,7 +833,7 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
if m1.Location.VirtualPath != "" && m1.Location.VirtualPath != m2.Location.VirtualPath {
|
||||
if m1.Location.AccessPath != "" && m1.Location.AccessPath != m2.Location.AccessPath {
|
||||
matches = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -70,4 +70,10 @@ var DefaultArtifactIDToGroupID = map[string]string{
|
||||
"spring-webflow": "org.springframework.webflow",
|
||||
"spring-webflux": "org.springframework",
|
||||
"spring-webmvc": "org.springframework",
|
||||
"spring-velocity-support": "org.apache.velocity",
|
||||
"velocity": "org.apache.velocity",
|
||||
"velocity-engine-core": "org.apache.velocity",
|
||||
"velocity-engine-parent": "org.apache.velocity",
|
||||
"velocity-engine-scripting": "org.apache.velocity",
|
||||
"velocity-tools": "org.apache.velocity",
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
var _ generic.Parser = parseConaninfo
|
||||
|
||||
func parseConanMetadataFromFilePath(path string) (pkg.ConaninfoEntry, error) {
|
||||
// fullFilePath = str(reader.Location.VirtualPath)
|
||||
// fullFilePath = str(reader.Location.AccessPath)
|
||||
// Split the full patch into the folders we expect. I.e.:
|
||||
// $HOME/.conan/data/<pkg-name>/<pkg-version>/<user>/<channel>/package/<package_id>/conaninfo.txt
|
||||
re := regexp.MustCompile(`.*[/\\](?P<name>[^/\\]+)[/\\](?P<version>[^/\\]+)[/\\](?P<user>[^/\\]+)[/\\](?P<channel>[^/\\]+)[/\\]package[/\\](?P<id>[^/\\]+)[/\\]conaninfo\.txt`)
|
||||
|
||||
@@ -96,7 +96,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
|
||||
copyrightReader, copyrightLocation := fetchCopyrightContents(resolver, dbLocation, metadata)
|
||||
|
||||
if copyrightReader != nil && copyrightLocation != nil {
|
||||
defer internal.CloseAndLogError(copyrightReader, copyrightLocation.VirtualPath)
|
||||
defer internal.CloseAndLogError(copyrightReader, copyrightLocation.AccessPath)
|
||||
// attach the licenses
|
||||
licenseStrs := parseLicensesFromCopyright(copyrightReader)
|
||||
for _, licenseStr := range licenseStrs {
|
||||
@@ -147,7 +147,7 @@ func getAdditionalFileListing(resolver file.Resolver, dbLocation file.Location,
|
||||
md5Reader, md5Location := fetchMd5Contents(resolver, dbLocation, m)
|
||||
|
||||
if md5Reader != nil && md5Location != nil {
|
||||
defer internal.CloseAndLogError(md5Reader, md5Location.VirtualPath)
|
||||
defer internal.CloseAndLogError(md5Reader, md5Location.AccessPath)
|
||||
// attach the file list
|
||||
files = append(files, parseDpkgMD5Info(md5Reader)...)
|
||||
|
||||
@@ -158,7 +158,7 @@ func getAdditionalFileListing(resolver file.Resolver, dbLocation file.Location,
|
||||
conffilesReader, conffilesLocation := fetchConffileContents(resolver, dbLocation, m)
|
||||
|
||||
if conffilesReader != nil && conffilesLocation != nil {
|
||||
defer internal.CloseAndLogError(conffilesReader, conffilesLocation.VirtualPath)
|
||||
defer internal.CloseAndLogError(conffilesReader, conffilesLocation.AccessPath)
|
||||
// attach the file list
|
||||
files = append(files, parseDpkgConffileInfo(conffilesReader)...)
|
||||
|
||||
|
||||
@@ -49,9 +49,9 @@ func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.Locati
|
||||
return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err)
|
||||
}
|
||||
|
||||
rootName := getDepsJSONFilePrefix(reader.AccessPath())
|
||||
rootName := getDepsJSONFilePrefix(reader.Path())
|
||||
if rootName == "" {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.AccessPath())
|
||||
return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.Path())
|
||||
}
|
||||
var rootPkg *pkg.Package
|
||||
for nameVersion, lib := range depsDoc.Libraries {
|
||||
@@ -65,7 +65,7 @@ func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.Locati
|
||||
}
|
||||
}
|
||||
if rootPkg == nil {
|
||||
return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.AccessPath())
|
||||
return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.Path())
|
||||
}
|
||||
pkgs = append(pkgs, *rootPkg)
|
||||
pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg
|
||||
|
||||
@@ -127,7 +127,7 @@ func (c *Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.R
|
||||
}
|
||||
|
||||
discoveredPackages, discoveredRelationships, err := parser(resolver, &env, file.NewLocationReadCloser(location, contentReader))
|
||||
internal.CloseAndLogError(contentReader, location.VirtualPath)
|
||||
internal.CloseAndLogError(contentReader, location.AccessPath)
|
||||
if err != nil {
|
||||
logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed")
|
||||
continue
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
func Test_Cataloger(t *testing.T) {
|
||||
allParsedPaths := make(map[string]bool)
|
||||
parser := func(resolver file.Resolver, env *Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
allParsedPaths[reader.AccessPath()] = true
|
||||
allParsedPaths[reader.Path()] = true
|
||||
contents, err := io.ReadAll(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
|
||||
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
|
||||
CPEs: []cpe.CPE{stdlibCpe},
|
||||
Locations: location,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Metadata: pkg.GolangBinaryBuildinfoEntry{
|
||||
|
||||
@@ -61,7 +61,7 @@ func NewCatalogTester() *CatalogTester {
|
||||
}
|
||||
|
||||
func DefaultLocationComparer(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.AccessPath, y.AccessPath)
|
||||
}
|
||||
|
||||
func DefaultLicenseComparer(x, y pkg.License) bool {
|
||||
@@ -161,7 +161,7 @@ func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *Cat
|
||||
|
||||
func (p *CatalogTester) IgnoreLocationLayer() *CatalogTester {
|
||||
p.locationComparer = func(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.AccessPath, y.AccessPath)
|
||||
}
|
||||
|
||||
// we need to update the license comparer to use the ignored location layer
|
||||
|
||||
@@ -87,7 +87,7 @@ func uniquePkgKey(groupID string, p *pkg.Package) string {
|
||||
// and parse nested archives or ignore them.
|
||||
func newJavaArchiveParser(reader file.LocationReadCloser, detectNested bool, cfg Config) (*archiveParser, func(), error) {
|
||||
// fetch the last element of the virtual path
|
||||
virtualElements := strings.Split(reader.AccessPath(), ":")
|
||||
virtualElements := strings.Split(reader.Path(), ":")
|
||||
currentFilepath := virtualElements[len(virtualElements)-1]
|
||||
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(currentFilepath, reader)
|
||||
@@ -208,7 +208,7 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
),
|
||||
Type: j.fileInfo.pkgType(),
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: j.location.AccessPath(),
|
||||
VirtualPath: j.location.Path(),
|
||||
Manifest: manifest,
|
||||
ArchiveDigests: digests,
|
||||
},
|
||||
@@ -250,9 +250,43 @@ func (j *archiveParser) parseLicenses(manifest *pkg.JavaManifest) ([]pkg.License
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find any licenses in the archive so far, we'll try again in Maven Central using groupIDFromJavaMetadata
|
||||
if len(licenses) == 0 && j.cfg.UseNetwork {
|
||||
licenses = findLicenseFromJavaMetadata(name, manifest, version, j, licenses)
|
||||
}
|
||||
|
||||
return licenses, name, version, nil
|
||||
}
|
||||
|
||||
func findLicenseFromJavaMetadata(name string, manifest *pkg.JavaManifest, version string, j *archiveParser, licenses []pkg.License) []pkg.License {
|
||||
var groupID = name
|
||||
if gID := groupIDFromJavaMetadata(name, pkg.JavaArchive{Manifest: manifest}); gID != "" {
|
||||
groupID = gID
|
||||
}
|
||||
pomLicenses, err := recursivelyFindLicensesFromParentPom(groupID, name, version, j.cfg)
|
||||
if err != nil {
|
||||
log.Tracef("unable to get parent pom from Maven central: %v", err)
|
||||
}
|
||||
|
||||
if len(pomLicenses) == 0 {
|
||||
// Try removing the last part of the groupId, as sometimes it duplicates the artifactId
|
||||
packages := strings.Split(groupID, ".")
|
||||
groupID = strings.Join(packages[:len(packages)-1], ".")
|
||||
pomLicenses, err = recursivelyFindLicensesFromParentPom(groupID, name, version, j.cfg)
|
||||
if err != nil {
|
||||
log.Tracef("unable to get parent pom from Maven central: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pomLicenses) > 0 {
|
||||
pkgLicenses := pkg.NewLicensesFromLocation(j.location, pomLicenses...)
|
||||
if pkgLicenses != nil {
|
||||
licenses = append(licenses, pkgLicenses...)
|
||||
}
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
type parsedPomProject struct {
|
||||
*pkg.JavaPomProject
|
||||
Licenses []pkg.License
|
||||
@@ -444,7 +478,7 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(parentPkg *pkg.Package) ([
|
||||
pomProject = proj
|
||||
}
|
||||
|
||||
pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.location)
|
||||
pkgFromPom := newPackageFromMavenData(propertiesObj, pomProject, parentPkg, j.location, j.cfg)
|
||||
if pkgFromPom != nil {
|
||||
pkgs = append(pkgs, *pkgFromPom)
|
||||
}
|
||||
@@ -526,7 +560,7 @@ func discoverPkgsFromOpeners(location file.Location, openers map[string]intFile.
|
||||
for pathWithinArchive, archiveOpener := range openers {
|
||||
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(location, pathWithinArchive, archiveOpener, cfg)
|
||||
if err != nil {
|
||||
log.WithFields("location", location.AccessPath()).Warnf("unable to discover java packages from opener: %+v", err)
|
||||
log.WithFields("location", location.Path()).Warnf("unable to discover java packages from opener: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -559,9 +593,9 @@ func discoverPkgsFromOpener(location file.Location, pathWithinArchive string, ar
|
||||
}
|
||||
}()
|
||||
|
||||
nestedPath := fmt.Sprintf("%s:%s", location.AccessPath(), pathWithinArchive)
|
||||
nestedPath := fmt.Sprintf("%s:%s", location.Path(), pathWithinArchive)
|
||||
nestedLocation := file.NewLocationFromCoordinates(location.Coordinates)
|
||||
nestedLocation.VirtualPath = nestedPath
|
||||
nestedLocation.AccessPath = nestedPath
|
||||
gap := newGenericArchiveParserAdapter(cfg)
|
||||
nestedPkgs, nestedRelationships, err := gap.parseJavaArchive(nil, nil, file.LocationReadCloser{
|
||||
Location: nestedLocation,
|
||||
@@ -584,7 +618,7 @@ func pomPropertiesByParentPath(archivePath string, location file.Location, extra
|
||||
for filePath, fileContents := range contentsOfMavenPropertiesFiles {
|
||||
pomProperties, err := parsePomProperties(filePath, strings.NewReader(fileContents))
|
||||
if err != nil {
|
||||
log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.properties: %+v", err)
|
||||
log.WithFields("contents-path", filePath, "location", location.Path()).Warnf("failed to parse pom.properties: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -614,7 +648,7 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
|
||||
// TODO: when we support locations of paths within archives we should start passing the specific pom.xml location object instead of the top jar
|
||||
pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents), location)
|
||||
if err != nil {
|
||||
log.WithFields("contents-path", filePath, "location", location.AccessPath()).Warnf("failed to parse pom.xml: %+v", err)
|
||||
log.WithFields("contents-path", filePath, "location", location.Path()).Warnf("failed to parse pom.xml: %+v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -635,7 +669,7 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
|
||||
|
||||
// newPackageFromMavenData processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
|
||||
func newPackageFromMavenData(pomProperties pkg.JavaPomProperties, parsedPomProject *parsedPomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package {
|
||||
func newPackageFromMavenData(pomProperties pkg.JavaPomProperties, parsedPomProject *parsedPomProject, parentPkg *pkg.Package, location file.Location, cfg Config) *pkg.Package {
|
||||
// keep the artifact name within the virtual path if this package does not match the parent package
|
||||
vPathSuffix := ""
|
||||
groupID := ""
|
||||
@@ -655,11 +689,14 @@ func newPackageFromMavenData(pomProperties pkg.JavaPomProperties, parsedPomProje
|
||||
// https://github.com/anchore/syft/issues/1944
|
||||
vPathSuffix += ":" + pomProperties.GroupID + ":" + pomProperties.ArtifactID
|
||||
}
|
||||
virtualPath := location.AccessPath() + vPathSuffix
|
||||
virtualPath := location.Path() + vPathSuffix
|
||||
|
||||
var pkgPomProject *pkg.JavaPomProject
|
||||
licenses := make([]pkg.License, 0)
|
||||
if parsedPomProject != nil {
|
||||
if cfg.UseNetwork {
|
||||
findPomLicenses(parsedPomProject, cfg)
|
||||
}
|
||||
pkgPomProject = parsedPomProject.JavaPomProject
|
||||
licenses = append(licenses, parsedPomProject.Licenses...)
|
||||
}
|
||||
|
||||
@@ -1089,7 +1089,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
}
|
||||
test.expectedParent.Locations = locations
|
||||
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, file.NewLocation(virtualPath))
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, file.NewLocation(virtualPath), Config{})
|
||||
if test.expectedPackage == nil {
|
||||
require.Nil(t, actualPackage)
|
||||
} else {
|
||||
|
||||
@@ -108,7 +108,7 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error)
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
||||
func extractNameFromApacheMavenBundlePlugin(manifest *pkg.JavaManifest) string {
|
||||
// special case: from https://svn.apache.org/repos/asf/felix/releases/maven-bundle-plugin-1.2.0/doc/maven-bundle-plugin-bnd.html
|
||||
// "<Bundle-SymbolicName> is assumed to be "${groupId}.${artifactId}"."
|
||||
//
|
||||
@@ -127,10 +127,17 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string
|
||||
//
|
||||
if manifest != nil {
|
||||
if strings.Contains(manifest.Main["Created-By"], "Apache Maven Bundle Plugin") {
|
||||
if v := manifest.Main["Bundle-SymbolicName"]; v != "" {
|
||||
if symbolicName := manifest.Main["Bundle-SymbolicName"]; symbolicName != "" {
|
||||
// It is possible that `Bundle-SymbolicName` is just the groupID (like in the case of
|
||||
// https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.25.0/google-oauth-client-1.25.0.jar),
|
||||
// so if `Implementation-Vendor-Id` is equal to `Bundle-SymbolicName`, bail on this logic
|
||||
if vendorID := manifest.Main["Implementation-Vendor-Id"]; vendorID != "" && vendorID == symbolicName {
|
||||
return ""
|
||||
}
|
||||
|
||||
// the problem with this approach is that we don't have a strong indication of the artifactId
|
||||
// not having a "." in it. However, by convention it is unlikely that an artifactId would have a ".".
|
||||
fields := strings.Split(v, ".")
|
||||
fields := strings.Split(symbolicName, ".")
|
||||
|
||||
// grab the last field, this is the artifactId. Note: because of [3] we do not know if this value is
|
||||
// correct. That is, a group id of "commons-logging" may have caused BND to swap out the reference to
|
||||
@@ -143,6 +150,15 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
||||
name := extractNameFromApacheMavenBundlePlugin(manifest)
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
// the filename tends to be the next-best reference for the package name
|
||||
if filenameObj.name != "" {
|
||||
if strings.Contains(filenameObj.name, ".") {
|
||||
|
||||
@@ -209,6 +209,30 @@ func TestSelectName(t *testing.T) {
|
||||
archive: newJavaArchiveFilename("/something/com.atlassian.gadgets.atlassian-gadgets-api.jar"),
|
||||
expected: "atlassian-gadgets-api",
|
||||
},
|
||||
{
|
||||
// example: pkg:maven/com.google.oauth-client/google-oauth-client@1.25.0
|
||||
desc: "skip Apache Maven Bundle Plugin logic if symbolic name is same as vendor id",
|
||||
manifest: pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Bundle-DocURL": "http://www.google.com/",
|
||||
"Bundle-License": "http://www.apache.org/licenses/LICENSE-2.0.txt",
|
||||
"Bundle-ManifestVersion": "2",
|
||||
"Bundle-Name": "Google OAuth Client Library for Java",
|
||||
"Bundle-RequiredExecutionEnvironment": "JavaSE-1.6",
|
||||
"Bundle-SymbolicName": "com.google.oauth-client",
|
||||
"Bundle-Vendor": "Google",
|
||||
"Bundle-Version": "1.25.0",
|
||||
"Created-By": "Apache Maven Bundle Plugin",
|
||||
"Export-Package": "com.google.api.client.auth.openidconnect;uses:=\"com.google.api.client.auth.oauth2,com.google.api.client.json,com.google.api.client.json.webtoken,com.google.api.client.util\";version=\"1.25.0\",com.google.api.client.auth.oauth;uses:=\"com.google.api.client.http,com.google.api.client.util\";version=\"1.25.0\",com.google.api.client.auth.oauth2;uses:=\"com.google.api.client.http,com.google.api.client.json,com.google.api.client.util,com.google.api.client.util.store\";version=\"1.25.0\"",
|
||||
"Implementation-Title": "Google OAuth Client Library for Java",
|
||||
"Implementation-Vendor": "Google",
|
||||
"Implementation-Vendor-Id": "com.google.oauth-client",
|
||||
"Implementation-Version": "1.25.0",
|
||||
},
|
||||
},
|
||||
archive: newJavaArchiveFilename("/something/google-oauth-client-1.25.0.jar"),
|
||||
expected: "google-oauth-client",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -57,7 +57,7 @@ func newGenericTarWrappedJavaArchiveParser(cfg Config) genericTarWrappedJavaArch
|
||||
}
|
||||
|
||||
func (gtp genericTarWrappedJavaArchiveParser) parseTarWrappedJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.Path(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
|
||||
@@ -29,7 +29,7 @@ func newGenericZipWrappedJavaArchiveParser(cfg Config) genericZipWrappedJavaArch
|
||||
}
|
||||
|
||||
func (gzp genericZipWrappedJavaArchiveParser) parseZipWrappedJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader)
|
||||
contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.Path(), reader)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
|
||||
@@ -64,7 +64,7 @@ func parsePackageJSON(_ file.Resolver, _ *generic.Environment, reader file.Locat
|
||||
}
|
||||
|
||||
if !p.hasNameAndVersionValues() {
|
||||
log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", reader.AccessPath())
|
||||
log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", reader.Path())
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ type packageLockLicense []string
|
||||
func parsePackageLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find package-lock.json files in the node_modules directories, skip those
|
||||
// as the whole purpose of the lock file is for the specific dependencies of the root project
|
||||
if pathContainsNodeModulesDirectory(reader.AccessPath()) {
|
||||
if pathContainsNodeModulesDirectory(reader.Path()) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ const (
|
||||
func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find yarn.lock files in the node_modules directories, skip those
|
||||
// as the whole purpose of the lock file is for the specific dependencies of the project
|
||||
if pathContainsNodeModulesDirectory(reader.AccessPath()) {
|
||||
if pathContainsNodeModulesDirectory(reader.Path()) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ func fetchTopLevelPackages(resolver file.Resolver, metadataLocation file.Locatio
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(topLevelContents, topLevelLocation.VirtualPath)
|
||||
defer internal.CloseAndLogError(topLevelContents, topLevelLocation.AccessPath)
|
||||
|
||||
scanner := bufio.NewScanner(topLevelContents)
|
||||
for scanner.Scan() {
|
||||
@@ -162,7 +162,7 @@ func fetchDirectURLData(resolver file.Resolver, metadataLocation file.Location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(directURLContents, directURLLocation.VirtualPath)
|
||||
defer internal.CloseAndLogError(directURLContents, directURLLocation.AccessPath)
|
||||
|
||||
buffer, err := io.ReadAll(directURLContents)
|
||||
if err != nil {
|
||||
@@ -191,7 +191,7 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(metadataContents, metadataLocation.VirtualPath)
|
||||
defer internal.CloseAndLogError(metadataContents, metadataLocation.AccessPath)
|
||||
|
||||
pd, err := parseWheelOrEggMetadata(metadataLocation.RealPath, metadataContents)
|
||||
if err != nil {
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestLicenseSet_Add(t *testing.T) {
|
||||
}
|
||||
|
||||
func defaultLocationComparer(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.AccessPath, y.AccessPath)
|
||||
}
|
||||
|
||||
func defaultLicenseComparer(x, y License) bool {
|
||||
|
||||
@@ -429,7 +429,7 @@ func licenseComparer(x, y License) bool {
|
||||
}
|
||||
|
||||
func locationComparer(x, y file.Location) bool {
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.AccessPath, y.AccessPath)
|
||||
}
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
|
||||
@@ -108,10 +108,6 @@ func deriveIDFromFile(cfg FileConfig) (artifact.ID, string) {
|
||||
info += fmt.Sprintf(":%s@%s", cfg.Alias.Name, cfg.Alias.Version)
|
||||
}
|
||||
|
||||
if d != "" {
|
||||
d = fmt.Sprintf("sha256:%s", d)
|
||||
}
|
||||
|
||||
return artifactIDFromDigest(digest.SHA256.FromString(info).String()), d
|
||||
}
|
||||
|
||||
|
||||
@@ -211,10 +211,11 @@ func createArchive(t testing.TB, sourceDirPath, destinationArchivePath string, l
|
||||
|
||||
func Test_FileSource_ID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg FileConfig
|
||||
want artifact.ID
|
||||
wantErr require.ErrorAssertionFunc
|
||||
name string
|
||||
cfg FileConfig
|
||||
want artifact.ID
|
||||
wantDigest string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -236,9 +237,10 @@ func Test_FileSource_ID(t *testing.T) {
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "with path",
|
||||
cfg: FileConfig{Path: "./test-fixtures/image-simple/Dockerfile"},
|
||||
want: artifact.ID("db7146472cf6d49b3ac01b42812fb60020b0b4898b97491b21bb690c808d5159"),
|
||||
name: "with path",
|
||||
cfg: FileConfig{Path: "./test-fixtures/image-simple/Dockerfile"},
|
||||
want: artifact.ID("db7146472cf6d49b3ac01b42812fb60020b0b4898b97491b21bb690c808d5159"),
|
||||
wantDigest: "sha256:38601c0bb4269a10ce1d00590ea7689c1117dd9274c758653934ab4f2016f80f",
|
||||
},
|
||||
{
|
||||
name: "with path and alias",
|
||||
@@ -249,7 +251,8 @@ func Test_FileSource_ID(t *testing.T) {
|
||||
Version: "version-me-this!",
|
||||
},
|
||||
},
|
||||
want: artifact.ID("3c713003305ac6605255cec8bf4ea649aa44b2b9a9f3a07bd683869d1363438a"),
|
||||
want: artifact.ID("3c713003305ac6605255cec8bf4ea649aa44b2b9a9f3a07bd683869d1363438a"),
|
||||
wantDigest: "sha256:38601c0bb4269a10ce1d00590ea7689c1117dd9274c758653934ab4f2016f80f",
|
||||
},
|
||||
{
|
||||
name: "other fields do not affect ID",
|
||||
@@ -259,7 +262,8 @@ func Test_FileSource_ID(t *testing.T) {
|
||||
Paths: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
want: artifact.ID("db7146472cf6d49b3ac01b42812fb60020b0b4898b97491b21bb690c808d5159"),
|
||||
want: artifact.ID("db7146472cf6d49b3ac01b42812fb60020b0b4898b97491b21bb690c808d5159"),
|
||||
wantDigest: "sha256:38601c0bb4269a10ce1d00590ea7689c1117dd9274c758653934ab4f2016f80f",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -272,7 +276,8 @@ func Test_FileSource_ID(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equalf(t, tt.want, s.ID(), "ID()")
|
||||
assert.Equalf(t, tt.want, s.ID(), "ID() mismatch")
|
||||
assert.Equalf(t, tt.wantDigest, s.digestForVersion, "digestForVersion mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type StereoscopeImageSourceMetadata struct {
|
||||
Architecture string `json:"architecture"`
|
||||
Variant string `json:"architectureVariant,omitempty"`
|
||||
OS string `json:"os"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// StereoscopeLayerMetadata represents all static metadata that defines what a container image layer is.
|
||||
@@ -48,6 +49,7 @@ func NewStereoscopeImageMetadata(img *image.Image, userInput string) Stereoscope
|
||||
Architecture: img.Metadata.Architecture,
|
||||
Variant: img.Metadata.Variant,
|
||||
OS: img.Metadata.OS,
|
||||
Labels: img.Metadata.Config.Config.Labels,
|
||||
}
|
||||
|
||||
// populate image metadata
|
||||
|
||||
@@ -192,6 +192,7 @@ func imageMetadataFromStereoscopeImage(img *image.Image, reference string) Stere
|
||||
Architecture: img.Metadata.Architecture,
|
||||
Variant: img.Metadata.Variant,
|
||||
OS: img.Metadata.OS,
|
||||
Labels: img.Metadata.Config.Config.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user