mirror of
https://github.com/anchore/syft.git
synced 2026-10-25 03:22:22 +01:00
Compare commits
@@ -0,0 +1 @@
|
||||
enforce-v0: true # don't make breaking-change label bump major version before 1.0.
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Add to OSS board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- transferred
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
|
||||
run:
|
||||
uses: "anchore/workflows/.github/workflows/oss-project-board-add.yaml@main"
|
||||
secrets:
|
||||
token: ${{ secrets.OSS_PROJECT_GH_TOKEN }}
|
||||
@@ -10,13 +10,13 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
|
||||
GLOW_CMD = $(TEMP_DIR)/glow
|
||||
|
||||
# Tool versions #################################
|
||||
GOLANGCILINT_VERSION := v1.53.2
|
||||
GOLANGCILINT_VERSION := v1.53.3
|
||||
GOSIMPORTS_VERSION := v0.3.8
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.6.0
|
||||
GORELEASER_VERSION := v1.18.2
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v2.0.2
|
||||
COSIGN_VERSION := v2.1.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
GLOW_VERSION := v1.5.1
|
||||
|
||||
|
||||
@@ -150,6 +150,12 @@ This default behavior can be overridden with the `default-image-pull-source` con
|
||||
|
||||
### Default Cataloger Configuration by scan type
|
||||
|
||||
Syft uses different default sets of catalogers depending on what it is scanning: a container image or a directory on disk. The default catalogers for an image scan assumes that package installation steps have already been completed. For example, Syft will identify Python packages that have egg or wheel metadata files under a site-packages directory, since this indicates software actually installed on an image.
|
||||
|
||||
However, if you are scanning a directory, Syft doesn't assume that all relevant software is installed, and will use catalogers that can identify declared dependencies that may not yet be installed on the final system: for example, dependencies listed in a Python requirements.txt.
|
||||
|
||||
You can override the list of enabled/disabled catalogers by using the "catalogers" keyword in the [Syft configuration file](https://github.com/anchore/syft#configuration).
|
||||
|
||||
##### Image Scanning:
|
||||
- alpmdb
|
||||
- apkdb
|
||||
|
||||
+102
-126
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/anchore/syft/cmd/syft/cli/packages"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
@@ -33,17 +34,6 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to write to report destination: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
fmt.Printf("unable to close report destination: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// could be an image or a directory, with or without a scheme
|
||||
// TODO: validate that source is image
|
||||
userInput := args[0]
|
||||
@@ -62,7 +52,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
subscription := eventBus.Subscribe()
|
||||
|
||||
return eventloop.EventLoop(
|
||||
execWorker(app, *si, writer),
|
||||
execWorker(app, *si),
|
||||
eventloop.SetupSignals(),
|
||||
subscription,
|
||||
stereoscope.Cleanup,
|
||||
@@ -70,7 +60,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
)
|
||||
}
|
||||
|
||||
func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
|
||||
func buildSBOM(app *config.Application, si source.Input, errs chan error) (*sbom.SBOM, error) {
|
||||
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
@@ -88,103 +78,120 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
|
||||
return nil, fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
}
|
||||
|
||||
// note: only works for single format no multi writer support
|
||||
sBytes, err := writer.Bytes(*s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to build SBOM bytes: %w", err)
|
||||
}
|
||||
|
||||
return sBytes, nil
|
||||
return s, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||
func execWorker(app *config.Application, si source.Input) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
sBytes, err := buildSBOM(app, si, writer, errs)
|
||||
defer bus.Publish(partybus.Event{Type: event.Exit})
|
||||
|
||||
s, err := buildSBOM(app, si, errs)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to build SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add multi writer support
|
||||
for _, o := range app.Outputs {
|
||||
f, err := os.CreateTemp("", o)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create temp file: %w", err)
|
||||
return
|
||||
}
|
||||
// note: ValidateOutputOptions ensures that there is no more than one output type
|
||||
o := app.Outputs[0]
|
||||
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
f, err := os.CreateTemp("", o)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create temp file: %w", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if _, err := f.Write(sBytes); err != nil {
|
||||
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: what other validation here besides binary name?
|
||||
cmd := "cosign"
|
||||
if !commandExists(cmd) {
|
||||
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
|
||||
return
|
||||
}
|
||||
|
||||
// Select Cosign predicate type based on defined output type
|
||||
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
||||
var predicateType string
|
||||
switch strings.ToLower(o) {
|
||||
case "cyclonedx-json":
|
||||
predicateType = "cyclonedx"
|
||||
case "spdx-tag-value":
|
||||
predicateType = "spdx"
|
||||
case "spdx-json":
|
||||
predicateType = "spdxjson"
|
||||
default:
|
||||
predicateType = "custom"
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
|
||||
if app.Attest.Key != "" {
|
||||
args = append(args, "--key", app.Attest.Key)
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
if app.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
// bus adapter for ui to hook into stdout via an os pipe
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create os pipe: %w", err)
|
||||
return
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
b := &busWriter{r: r, w: w, mon: progress.NewManual(-1)}
|
||||
execCmd.Stdout = b
|
||||
execCmd.Stderr = b
|
||||
defer b.mon.SetCompleted()
|
||||
|
||||
// attest the SBOM
|
||||
err = execCmd.Run()
|
||||
if err != nil {
|
||||
b.mon.SetError(err)
|
||||
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
writer, err := options.MakeSBOMWriter(app.Outputs, f.Name(), app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create SBOM writer: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.Exit,
|
||||
Value: func() error { return nil },
|
||||
})
|
||||
if err := writer.Write(*s); err != nil {
|
||||
errs <- fmt.Errorf("unable to write SBOM to temp file: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: what other validation here besides binary name?
|
||||
cmd := "cosign"
|
||||
if !commandExists(cmd) {
|
||||
errs <- fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
|
||||
return
|
||||
}
|
||||
|
||||
// Select Cosign predicate type based on defined output type
|
||||
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
||||
var predicateType string
|
||||
switch strings.ToLower(o) {
|
||||
case "cyclonedx-json":
|
||||
predicateType = "cyclonedx"
|
||||
case "spdx-tag-value", "spdx-tv":
|
||||
predicateType = "spdx"
|
||||
case "spdx-json", "json":
|
||||
predicateType = "spdxjson"
|
||||
default:
|
||||
predicateType = "custom"
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--predicate", f.Name(), "--type", predicateType}
|
||||
if app.Attest.Key != "" {
|
||||
args = append(args, "--key", app.Attest.Key)
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
if app.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
log.WithFields("cmd", strings.Join(execCmd.Args, " ")).Trace("creating attestation")
|
||||
|
||||
// bus adapter for ui to hook into stdout via an os pipe
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("unable to create os pipe: %w", err)
|
||||
return
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
mon := progress.NewManual(-1)
|
||||
|
||||
bus.Publish(
|
||||
partybus.Event{
|
||||
Type: event.AttestationStarted,
|
||||
Source: monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
Default: "Create attestation",
|
||||
WhileRunning: "Creating attestation",
|
||||
OnSuccess: "Created attestation",
|
||||
},
|
||||
Context: "cosign",
|
||||
},
|
||||
Value: &monitor.ShellProgress{
|
||||
Reader: r,
|
||||
Manual: mon,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
execCmd.Stdout = w
|
||||
execCmd.Stderr = w
|
||||
|
||||
// attest the SBOM
|
||||
err = execCmd.Run()
|
||||
if err != nil {
|
||||
mon.SetError(err)
|
||||
errs <- fmt.Errorf("unable to attest SBOM: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
mon.SetCompleted()
|
||||
}()
|
||||
return errs
|
||||
}
|
||||
@@ -207,37 +214,6 @@ func ValidateOutputOptions(app *config.Application) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type busWriter struct {
|
||||
w *os.File
|
||||
r *os.File
|
||||
hasWritten bool
|
||||
mon *progress.Manual
|
||||
}
|
||||
|
||||
func (b *busWriter) Write(p []byte) (n int, err error) {
|
||||
if !b.hasWritten {
|
||||
b.hasWritten = true
|
||||
bus.Publish(
|
||||
partybus.Event{
|
||||
Type: event.AttestationStarted,
|
||||
Source: monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
Default: "Create attestation",
|
||||
WhileRunning: "Creating attestation",
|
||||
OnSuccess: "Created attestation",
|
||||
},
|
||||
Context: "cosign",
|
||||
},
|
||||
Value: &monitor.ShellProgress{
|
||||
Reader: b.r,
|
||||
Manual: b.mon,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
return b.w.Write(p)
|
||||
}
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
|
||||
@@ -14,17 +14,11 @@ import (
|
||||
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
writer, err := options.MakeSBOMWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// this can only be a SBOM file
|
||||
userInput := args[0]
|
||||
|
||||
|
||||
@@ -2,25 +2,37 @@ package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/formats"
|
||||
"github.com/anchore/syft/syft/formats/table"
|
||||
"github.com/anchore/syft/syft/formats/template"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
// makeWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
|
||||
var _ sbom.Writer = (*sbomMultiWriter)(nil)
|
||||
|
||||
var _ interface {
|
||||
io.Closer
|
||||
sbom.Writer
|
||||
} = (*sbomStreamWriter)(nil)
|
||||
|
||||
// MakeSBOMWriter creates a sbom.Writer for output or returns an error. this will either return a valid writer
|
||||
// or an error but neither both and if there is no error, sbom.Writer.Close() should be called
|
||||
func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Writer, error) {
|
||||
outputOptions, err := parseOutputs(outputs, defaultFile, templateFilePath)
|
||||
func MakeSBOMWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Writer, error) {
|
||||
outputOptions, err := parseSBOMOutputFlags(outputs, defaultFile, templateFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
writer, err := sbom.NewWriter(outputOptions...)
|
||||
writer, err := newSBOMMultiWriter(outputOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -28,8 +40,18 @@ func MakeWriter(outputs []string, defaultFile, templateFilePath string) (sbom.Wr
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// parseOptions utility to parse command-line option strings and retain the existing behavior of default format and file
|
||||
func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out []sbom.WriterOption, errs error) {
|
||||
// MakeSBOMWriterForFormat creates a sbom.Writer for for the given format or returns an error.
|
||||
func MakeSBOMWriterForFormat(format sbom.Format, path string) (sbom.Writer, error) {
|
||||
writer, err := newSBOMMultiWriter(newSBOMWriterDescription(format, path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// parseSBOMOutputFlags utility to parse command-line option strings and retain the existing behavior of default format and file
|
||||
func parseSBOMOutputFlags(outputs []string, defaultFile, templateFilePath string) (out []sbomWriterDescription, errs error) {
|
||||
// always should have one option -- we generally get the default of "table", but just make sure
|
||||
if len(outputs) == 0 {
|
||||
outputs = append(outputs, table.ID.String())
|
||||
@@ -63,7 +85,113 @@ func parseOutputs(outputs []string, defaultFile, templateFilePath string) (out [
|
||||
format = tmpl
|
||||
}
|
||||
|
||||
out = append(out, sbom.NewWriterOption(format, file))
|
||||
out = append(out, newSBOMWriterDescription(format, file))
|
||||
}
|
||||
return out, errs
|
||||
}
|
||||
|
||||
// sbomWriterDescription Format and path strings used to create sbom.Writer
|
||||
type sbomWriterDescription struct {
|
||||
Format sbom.Format
|
||||
Path string
|
||||
}
|
||||
|
||||
func newSBOMWriterDescription(f sbom.Format, p string) sbomWriterDescription {
|
||||
expandedPath, err := homedir.Expand(p)
|
||||
if err != nil {
|
||||
log.Warnf("could not expand given writer output path=%q: %w", p, err)
|
||||
// ignore errors
|
||||
expandedPath = p
|
||||
}
|
||||
return sbomWriterDescription{
|
||||
Format: f,
|
||||
Path: expandedPath,
|
||||
}
|
||||
}
|
||||
|
||||
// sbomMultiWriter holds a list of child sbom.Writers to apply all Write and Close operations to
|
||||
type sbomMultiWriter struct {
|
||||
writers []sbom.Writer
|
||||
}
|
||||
|
||||
type nopWriteCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (n nopWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newSBOMMultiWriter create all report writers from input options; if a file is not specified the given defaultWriter is used
|
||||
func newSBOMMultiWriter(options ...sbomWriterDescription) (_ *sbomMultiWriter, err error) {
|
||||
if len(options) == 0 {
|
||||
return nil, fmt.Errorf("no output options provided")
|
||||
}
|
||||
|
||||
out := &sbomMultiWriter{}
|
||||
|
||||
for _, option := range options {
|
||||
switch len(option.Path) {
|
||||
case 0:
|
||||
out.writers = append(out.writers, &sbomStreamWriter{
|
||||
format: option.Format,
|
||||
out: nopWriteCloser{Writer: os.Stdout},
|
||||
})
|
||||
default:
|
||||
// create any missing subdirectories
|
||||
dir := path.Dir(option.Path)
|
||||
if dir != "" {
|
||||
s, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(dir, 0755) // maybe should be os.ModePerm ?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return nil, fmt.Errorf("output path does not contain a valid directory: %s", option.Path)
|
||||
}
|
||||
}
|
||||
fileOut, err := os.OpenFile(option.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create report file: %w", err)
|
||||
}
|
||||
out.writers = append(out.writers, &sbomStreamWriter{
|
||||
format: option.Format,
|
||||
out: fileOut,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Write writes the SBOM to all writers
|
||||
func (m *sbomMultiWriter) Write(s sbom.SBOM) (errs error) {
|
||||
for _, w := range m.writers {
|
||||
err := w.Write(s)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, fmt.Errorf("unable to write SBOM: %w", err))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// sbomStreamWriter implements sbom.Writer for a given format and io.Writer, also providing a close function for cleanup
|
||||
type sbomStreamWriter struct {
|
||||
format sbom.Format
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// Write the provided SBOM to the data stream
|
||||
func (w *sbomStreamWriter) Write(s sbom.SBOM) error {
|
||||
defer w.Close()
|
||||
return w.format.Encode(w.out, s)
|
||||
}
|
||||
|
||||
// Close any resources, such as open files
|
||||
func (w *sbomStreamWriter) Close() error {
|
||||
if closer, ok := w.out.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func TestIsSupportedFormat(t *testing.T) {
|
||||
func Test_MakeSBOMWriter(t *testing.T) {
|
||||
tests := []struct {
|
||||
outputs []string
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
@@ -28,7 +34,198 @@ func TestIsSupportedFormat(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
_, err := MakeWriter(tt.outputs, "", "")
|
||||
_, err := MakeSBOMWriter(tt.outputs, "", "")
|
||||
tt.wantErr(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func dummyEncoder(io.Writer, sbom.SBOM) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dummyFormat(name string) sbom.Format {
|
||||
return sbom.NewFormat(sbom.AnyVersion, dummyEncoder, nil, nil, sbom.FormatID(name))
|
||||
}
|
||||
|
||||
func Test_newSBOMMultiWriter(t *testing.T) {
|
||||
type writerConfig struct {
|
||||
format string
|
||||
file string
|
||||
}
|
||||
|
||||
tmp := t.TempDir()
|
||||
|
||||
testName := func(options []sbomWriterDescription, err bool) string {
|
||||
var out []string
|
||||
for _, opt := range options {
|
||||
out = append(out, string(opt.Format.ID())+"="+opt.Path)
|
||||
}
|
||||
errs := ""
|
||||
if err {
|
||||
errs = "(err)"
|
||||
}
|
||||
return strings.Join(out, ", ") + errs
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
outputs []sbomWriterDescription
|
||||
err bool
|
||||
expected []writerConfig
|
||||
}{
|
||||
{
|
||||
outputs: []sbomWriterDescription{},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
outputs: []sbomWriterDescription{
|
||||
{
|
||||
Format: dummyFormat("table"),
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "table",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []sbomWriterDescription{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []sbomWriterDescription{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
Path: "test-2.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
file: "test-2.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []sbomWriterDescription{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
Path: "test-3/1.json",
|
||||
},
|
||||
{
|
||||
Format: dummyFormat("spdx-json"),
|
||||
Path: "test-3/2.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
file: "test-3/1.json",
|
||||
},
|
||||
{
|
||||
format: "spdx-json",
|
||||
file: "test-3/2.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []sbomWriterDescription{
|
||||
{
|
||||
Format: dummyFormat("text"),
|
||||
},
|
||||
{
|
||||
Format: dummyFormat("spdx-json"),
|
||||
Path: "test-4.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "text",
|
||||
},
|
||||
{
|
||||
format: "spdx-json",
|
||||
file: "test-4.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(testName(test.outputs, test.err), func(t *testing.T) {
|
||||
outputs := test.outputs
|
||||
for i := range outputs {
|
||||
if outputs[i].Path != "" {
|
||||
outputs[i].Path = tmp + outputs[i].Path
|
||||
}
|
||||
}
|
||||
|
||||
mw, err := newSBOMMultiWriter(outputs...)
|
||||
|
||||
if test.err {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
assert.Len(t, mw.writers, len(test.expected))
|
||||
|
||||
for i, e := range test.expected {
|
||||
switch w := mw.writers[i].(type) {
|
||||
case *sbomStreamWriter:
|
||||
assert.Equal(t, string(w.format.ID()), e.format)
|
||||
if e.file != "" {
|
||||
assert.NotNil(t, w.out)
|
||||
} else {
|
||||
assert.NotNil(t, w.out)
|
||||
}
|
||||
if e.file != "" {
|
||||
assert.FileExists(t, tmp+e.file)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unknown writer type: %T", w)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newSBOMWriterDescription(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "expand home dir",
|
||||
path: "~/place.txt",
|
||||
expected: filepath.Join(homedir.Get(), "place.txt"),
|
||||
},
|
||||
{
|
||||
name: "passthrough other paths",
|
||||
path: "/other/place.txt",
|
||||
expected: "/other/place.txt",
|
||||
},
|
||||
{
|
||||
name: "no path",
|
||||
path: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := newSBOMWriterDescription(dummyFormat("table"), tt.path)
|
||||
assert.Equal(t, tt.expected, o.Path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft"
|
||||
@@ -29,17 +28,11 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
writer, err := options.MakeSBOMWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// could be an image or a directory, with or without a scheme
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource)
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/config"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft"
|
||||
@@ -28,19 +27,11 @@ import (
|
||||
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
f := syftjson.Format()
|
||||
writer, err := sbom.NewWriter(sbom.WriterOption{
|
||||
Format: f,
|
||||
Path: app.File,
|
||||
})
|
||||
writer, err := options.MakeSBOMWriterForFormat(f, app.File)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := writer.Close(); err != nil {
|
||||
log.Warnf("unable to write to report destination: %+v", err)
|
||||
}
|
||||
|
||||
// inform user at end of run that command will be removed
|
||||
deprecated := color.Style{color.Red, color.OpBold}.Sprint("DEPRECATED: This command will be removed in v1.0.0")
|
||||
fmt.Fprintln(os.Stderr, deprecated)
|
||||
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spdx/tools-golang v0.5.1
|
||||
github.com/spdx/tools-golang v0.5.2
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
@@ -42,9 +42,9 @@ require (
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.10.0
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/term v0.8.0
|
||||
golang.org/x/mod v0.11.0
|
||||
golang.org/x/net v0.11.0
|
||||
golang.org/x/term v0.9.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ require (
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e
|
||||
github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e
|
||||
github.com/dave/jennifer v1.6.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/docker v24.0.2+incompatible
|
||||
@@ -69,7 +69,7 @@ require (
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.23.0
|
||||
modernc.org/sqlite v1.23.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -144,8 +144,8 @@ require (
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
@@ -169,7 +169,7 @@ require (
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -98,8 +98,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e h1:YPWJxds1hKRedS92u7O6D6ULVOx1F2HGgS4CWqJdBYw=
|
||||
github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g=
|
||||
github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e h1:zhk3ZLtomMJ750nNCE+c24PonMzoO/SeL/4uTr1L9kM=
|
||||
github.com/anchore/stereoscope v0.0.0-20230627195312-cd49355d934e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -559,8 +559,8 @@ github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIn
|
||||
github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.1 h1:fJg3SVOGG+eIva9ZUBm/hvyA7PIPVFjRxUKe6fdAgwE=
|
||||
github.com/spdx/tools-golang v0.5.1/go.mod h1:/DRDQuBfB37HctM29YtrX1v+bXiVmT2OpQDalRmX9aU=
|
||||
github.com/spdx/tools-golang v0.5.2 h1:dtMNjJreWPe37584ajk7m/rQtfJaLpRMk7pUGgvekOg=
|
||||
github.com/spdx/tools-golang v0.5.2/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
@@ -677,8 +677,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
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=
|
||||
@@ -720,8 +720,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -770,8 +770,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.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
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=
|
||||
@@ -882,8 +882,8 @@ 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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -891,8 +891,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
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.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
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=
|
||||
@@ -905,8 +905,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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
||||
@@ -1177,8 +1177,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.23.0 h1:MWTFBI5H1WLnXpNBh/BTruBVqzzoh28DA0iOnlkkRaM=
|
||||
modernc.org/sqlite v1.23.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
// This file was generated by robots at 2023-03-22 18:00:44.589388 -0400 EDT m=+0.160679640
|
||||
// This file was generated by robots at 2023-06-20 11:37:07.979104 -0400 EDT m=+0.478800893
|
||||
// using data from https://spdx.org/licenses/licenses.json
|
||||
package spdxlicense
|
||||
|
||||
const Version = "3.20"
|
||||
const Version = "3.21"
|
||||
|
||||
var licenseIDs = map[string]string{
|
||||
"0bsd": "0BSD",
|
||||
@@ -93,6 +93,11 @@ var licenseIDs = map[string]string{
|
||||
"artistic2": "Artistic-2.0",
|
||||
"artistic2.0": "Artistic-2.0",
|
||||
"artistic2.0.0": "Artistic-2.0",
|
||||
"aswfdigitalassets1": "ASWF-Digital-Assets-1.0",
|
||||
"aswfdigitalassets1.0": "ASWF-Digital-Assets-1.0",
|
||||
"aswfdigitalassets1.0.0": "ASWF-Digital-Assets-1.0",
|
||||
"aswfdigitalassets1.1": "ASWF-Digital-Assets-1.1",
|
||||
"aswfdigitalassets1.1.0": "ASWF-Digital-Assets-1.1",
|
||||
"baekmuk": "Baekmuk",
|
||||
"bahyph": "Bahyph",
|
||||
"barr": "Barr",
|
||||
@@ -108,6 +113,7 @@ var licenseIDs = map[string]string{
|
||||
"blueoak1": "BlueOak-1.0.0",
|
||||
"blueoak1.0": "BlueOak-1.0.0",
|
||||
"blueoak1.0.0": "BlueOak-1.0.0",
|
||||
"boehmgc": "Boehm-GC",
|
||||
"borceux": "Borceux",
|
||||
"briangladman3.0.0clause": "Brian-Gladman-3-Clause",
|
||||
"briangladman3.0clause": "Brian-Gladman-3-Clause",
|
||||
@@ -336,10 +342,13 @@ var licenseIDs = map[string]string{
|
||||
"ccbysa3.0.0": "CC-BY-SA-3.0",
|
||||
"ccbysa3.0.0at": "CC-BY-SA-3.0-AT",
|
||||
"ccbysa3.0.0de": "CC-BY-SA-3.0-DE",
|
||||
"ccbysa3.0.0igo": "CC-BY-SA-3.0-IGO",
|
||||
"ccbysa3.0at": "CC-BY-SA-3.0-AT",
|
||||
"ccbysa3.0de": "CC-BY-SA-3.0-DE",
|
||||
"ccbysa3.0igo": "CC-BY-SA-3.0-IGO",
|
||||
"ccbysa3at": "CC-BY-SA-3.0-AT",
|
||||
"ccbysa3de": "CC-BY-SA-3.0-DE",
|
||||
"ccbysa3igo": "CC-BY-SA-3.0-IGO",
|
||||
"ccbysa4": "CC-BY-SA-4.0",
|
||||
"ccbysa4.0": "CC-BY-SA-4.0",
|
||||
"ccbysa4.0.0": "CC-BY-SA-4.0",
|
||||
@@ -440,6 +449,7 @@ var licenseIDs = map[string]string{
|
||||
"drl1.0": "DRL-1.0",
|
||||
"drl1.0.0": "DRL-1.0",
|
||||
"dsdp": "DSDP",
|
||||
"dtoa": "dtoa",
|
||||
"dvipdfm": "dvipdfm",
|
||||
"ecl1": "ECL-1.0",
|
||||
"ecl1.0": "ECL-1.0",
|
||||
@@ -638,6 +648,9 @@ var licenseIDs = map[string]string{
|
||||
"imlib2.0": "Imlib2",
|
||||
"imlib2.0.0": "Imlib2",
|
||||
"infozip": "Info-ZIP",
|
||||
"innernet2": "Inner-Net-2.0",
|
||||
"innernet2.0": "Inner-Net-2.0",
|
||||
"innernet2.0.0": "Inner-Net-2.0",
|
||||
"intel": "Intel",
|
||||
"intelacpi": "Intel-ACPI",
|
||||
"interbase1": "Interbase-1.0",
|
||||
@@ -663,8 +676,11 @@ var licenseIDs = map[string]string{
|
||||
"lal1.3": "LAL-1.3",
|
||||
"lal1.3.0": "LAL-1.3",
|
||||
"latex2.0.0e": "Latex2e",
|
||||
"latex2.0.0etranslatednotice": "Latex2e-translated-notice",
|
||||
"latex2.0e": "Latex2e",
|
||||
"latex2.0etranslatednotice": "Latex2e-translated-notice",
|
||||
"latex2e": "Latex2e",
|
||||
"latex2etranslatednotice": "Latex2e-translated-notice",
|
||||
"leptonica": "Leptonica",
|
||||
"lgpl2": "LGPL-2.0-only",
|
||||
"lgpl2+": "LGPL-2.0-or-later",
|
||||
@@ -717,7 +733,14 @@ var licenseIDs = map[string]string{
|
||||
"liliqrplus1": "LiLiQ-Rplus-1.1",
|
||||
"liliqrplus1.1": "LiLiQ-Rplus-1.1",
|
||||
"liliqrplus1.1.0": "LiLiQ-Rplus-1.1",
|
||||
"linuxmanpages1.0.0para": "Linux-man-pages-1-para",
|
||||
"linuxmanpages1.0para": "Linux-man-pages-1-para",
|
||||
"linuxmanpages1para": "Linux-man-pages-1-para",
|
||||
"linuxmanpagescopyleft": "Linux-man-pages-copyleft",
|
||||
"linuxmanpagescopyleft2.0.0para": "Linux-man-pages-copyleft-2-para",
|
||||
"linuxmanpagescopyleft2.0para": "Linux-man-pages-copyleft-2-para",
|
||||
"linuxmanpagescopyleft2para": "Linux-man-pages-copyleft-2-para",
|
||||
"linuxmanpagescopyleftvar": "Linux-man-pages-copyleft-var",
|
||||
"linuxopenib": "Linux-OpenIB",
|
||||
"loop": "LOOP",
|
||||
"lpl1": "LPL-1.0",
|
||||
@@ -746,6 +769,7 @@ var licenseIDs = map[string]string{
|
||||
"lzmasdk9to9.20": "LZMA-SDK-9.11-to-9.20",
|
||||
"makeindex": "MakeIndex",
|
||||
"martinbirgmeier": "Martin-Birgmeier",
|
||||
"metamail": "metamail",
|
||||
"minpack": "Minpack",
|
||||
"miros": "MirOS",
|
||||
"mit": "MIT",
|
||||
@@ -754,6 +778,7 @@ var licenseIDs = map[string]string{
|
||||
"mitcmu": "MIT-CMU",
|
||||
"mitenna": "MIT-enna",
|
||||
"mitfeh": "MIT-feh",
|
||||
"mitfestival": "MIT-Festival",
|
||||
"mitmodernvariant": "MIT-Modern-Variant",
|
||||
"mitnfa": "MITNFA",
|
||||
"mitopengroup": "MIT-open-group",
|
||||
@@ -810,6 +835,7 @@ var licenseIDs = map[string]string{
|
||||
"nicta1.0.0": "NICTA-1.0",
|
||||
"nistpd": "NIST-PD",
|
||||
"nistpdfallback": "NIST-PD-fallback",
|
||||
"nistsoftware": "NIST-Software",
|
||||
"nlod1": "NLOD-1.0",
|
||||
"nlod1.0": "NLOD-1.0",
|
||||
"nlod1.0.0": "NLOD-1.0",
|
||||
@@ -908,6 +934,9 @@ var licenseIDs = map[string]string{
|
||||
"oldap2.7.0": "OLDAP-2.7",
|
||||
"oldap2.8": "OLDAP-2.8",
|
||||
"oldap2.8.0": "OLDAP-2.8",
|
||||
"olfl1": "OLFL-1.3",
|
||||
"olfl1.3": "OLFL-1.3",
|
||||
"olfl1.3.0": "OLFL-1.3",
|
||||
"oml": "OML",
|
||||
"openpbs2": "OpenPBS-2.3",
|
||||
"openpbs2.3": "OpenPBS-2.3",
|
||||
@@ -916,6 +945,9 @@ var licenseIDs = map[string]string{
|
||||
"opl1": "OPL-1.0",
|
||||
"opl1.0": "OPL-1.0",
|
||||
"opl1.0.0": "OPL-1.0",
|
||||
"opluk3": "OPL-UK-3.0",
|
||||
"opluk3.0": "OPL-UK-3.0",
|
||||
"opluk3.0.0": "OPL-UK-3.0",
|
||||
"opubl1": "OPUBL-1.0",
|
||||
"opubl1.0": "OPUBL-1.0",
|
||||
"opubl1.0.0": "OPUBL-1.0",
|
||||
@@ -1007,6 +1039,9 @@ var licenseIDs = map[string]string{
|
||||
"sgib2": "SGI-B-2.0",
|
||||
"sgib2.0": "SGI-B-2.0",
|
||||
"sgib2.0.0": "SGI-B-2.0",
|
||||
"sgp4": "SGP4",
|
||||
"sgp4.0": "SGP4",
|
||||
"sgp4.0.0": "SGP4",
|
||||
"shl0.5": "SHL-0.5",
|
||||
"shl0.5.0": "SHL-0.5",
|
||||
"shl0.51": "SHL-0.51",
|
||||
@@ -1052,6 +1087,7 @@ var licenseIDs = map[string]string{
|
||||
"taprohl1.0.0": "TAPR-OHL-1.0",
|
||||
"tcl": "TCL",
|
||||
"tcpwrappers": "TCP-wrappers",
|
||||
"termreadkey": "TermReadKey",
|
||||
"tmate": "TMate",
|
||||
"torque1": "TORQUE-1.1",
|
||||
"torque1.1": "TORQUE-1.1",
|
||||
@@ -1079,6 +1115,7 @@ var licenseIDs = map[string]string{
|
||||
"unicodedfs2016.0": "Unicode-DFS-2016",
|
||||
"unicodedfs2016.0.0": "Unicode-DFS-2016",
|
||||
"unicodetou": "Unicode-TOU",
|
||||
"unixcrypt": "UnixCrypt",
|
||||
"unlicense": "Unlicense",
|
||||
"upl1": "UPL-1.0",
|
||||
"upl1.0": "UPL-1.0",
|
||||
@@ -1103,6 +1140,7 @@ var licenseIDs = map[string]string{
|
||||
"watcom1": "Watcom-1.0",
|
||||
"watcom1.0": "Watcom-1.0",
|
||||
"watcom1.0.0": "Watcom-1.0",
|
||||
"widgetworkshop": "Widget-Workshop",
|
||||
"wsuipa": "Wsuipa",
|
||||
"wtfpl": "WTFPL",
|
||||
"wxwindows": "wxWindows",
|
||||
@@ -1112,7 +1150,11 @@ var licenseIDs = map[string]string{
|
||||
"x11.0.0distributemodificationsvariant": "X11-distribute-modifications-variant",
|
||||
"x11.0distributemodificationsvariant": "X11-distribute-modifications-variant",
|
||||
"x11distributemodificationsvariant": "X11-distribute-modifications-variant",
|
||||
"xdebug1": "Xdebug-1.03",
|
||||
"xdebug1.03": "Xdebug-1.03",
|
||||
"xdebug1.03.0": "Xdebug-1.03",
|
||||
"xerox": "Xerox",
|
||||
"xfig": "Xfig",
|
||||
"xfree861": "XFree86-1.1",
|
||||
"xfree861.1": "XFree86-1.1",
|
||||
"xfree861.1.0": "XFree86-1.1",
|
||||
|
||||
+1
-1
@@ -22,5 +22,5 @@ func IDByHash(obj interface{}) (ID, error) {
|
||||
return "", fmt.Errorf("could not build ID for object=%+v: %w", obj, err)
|
||||
}
|
||||
|
||||
return ID(fmt.Sprintf("%x", f)), nil
|
||||
return ID(fmt.Sprintf("%016x", f)), nil
|
||||
}
|
||||
|
||||
@@ -12,40 +12,36 @@ import (
|
||||
|
||||
// This should be a function that just surfaces licenses already validated in the package struct
|
||||
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||
spdxc, otherc, ex := separateLicenses(p)
|
||||
if len(otherc) > 0 {
|
||||
spdx, other, ex := separateLicenses(p)
|
||||
out := spdx
|
||||
out = append(out, other...)
|
||||
|
||||
if len(other) > 0 || len(spdx) > 0 {
|
||||
// found non spdx related licenses
|
||||
// build individual license choices for each
|
||||
// complex expressions are not combined and set as NAME fields
|
||||
for _, e := range ex {
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
if e == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: e,
|
||||
},
|
||||
})
|
||||
}
|
||||
otherc = append(otherc, spdxc...)
|
||||
return &otherc
|
||||
}
|
||||
|
||||
if len(spdxc) > 0 {
|
||||
for _, l := range ex {
|
||||
spdxc = append(spdxc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: l,
|
||||
},
|
||||
} else if len(ex) > 0 {
|
||||
// only expressions found
|
||||
e := mergeSPDX(ex)
|
||||
if e != "" {
|
||||
out = append(out, cyclonedx.LicenseChoice{
|
||||
Expression: e,
|
||||
})
|
||||
}
|
||||
return &spdxc
|
||||
}
|
||||
|
||||
if len(ex) > 0 {
|
||||
// only expressions found
|
||||
var expressions cyclonedx.Licenses
|
||||
expressions = append(expressions, cyclonedx.LicenseChoice{
|
||||
Expression: mergeSPDX(ex),
|
||||
})
|
||||
return &expressions
|
||||
if len(out) > 0 {
|
||||
return &out
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -185,20 +181,20 @@ func reduceOuter(expression string) string {
|
||||
|
||||
for _, c := range expression {
|
||||
if string(c) == "(" && openCount > 0 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
_, _ = fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == "(" {
|
||||
openCount++
|
||||
continue
|
||||
}
|
||||
if string(c) == ")" && openCount > 1 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
_, _ = fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == ")" {
|
||||
openCount--
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
_, _ = fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
|
||||
@@ -18,9 +18,8 @@ func Test_encodeLicense(t *testing.T) {
|
||||
expected *cyclonedx.Licenses
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: nil,
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
@@ -48,12 +47,12 @@ func Test_encodeLicense(t *testing.T) {
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FOOBAR",
|
||||
ID: "MIT",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
Name: "FOOBAR",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -95,17 +94,6 @@ func Test_encodeLicense(t *testing.T) {
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FakeLicense",
|
||||
URL: "htts://someurl.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
@@ -118,6 +106,17 @@ func Test_encodeLicense(t *testing.T) {
|
||||
URL: "https://spdx.org/licenses/MIT.html",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FakeLicense",
|
||||
URL: "htts://someurl.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e7c88bd18e11b0b",
|
||||
"id": "0e7c88bd18e11b0b",
|
||||
"location": {
|
||||
"path": "/a/place/a"
|
||||
},
|
||||
|
||||
+3
-3
@@ -56,13 +56,13 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Collection,
|
||||
// otherwise conditionally use the correct set of loggers based on the input type (container image or directory)
|
||||
switch src.Metadata.Scheme {
|
||||
case source.ImageScheme:
|
||||
log.Info("cataloging image")
|
||||
log.Info("cataloging an image")
|
||||
catalogers = cataloger.ImageCatalogers(cfg)
|
||||
case source.FileScheme:
|
||||
log.Info("cataloging file")
|
||||
log.Info("cataloging a file")
|
||||
catalogers = cataloger.AllCatalogers(cfg)
|
||||
case source.DirectoryScheme:
|
||||
log.Info("cataloging directory")
|
||||
log.Info("cataloging a directory")
|
||||
catalogers = cataloger.DirectoryCatalogers(cfg)
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -38,6 +38,23 @@ type pubspecLockDescription struct {
|
||||
ResolvedRef string `yaml:"resolved-ref" mapstructure:"resolved-ref"`
|
||||
}
|
||||
|
||||
func (p *pubspecLockDescription) UnmarshalYAML(value *yaml.Node) error {
|
||||
type pld pubspecLockDescription
|
||||
var p2 pld
|
||||
|
||||
if value.Decode(&p.Name) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := value.Decode(&p2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*p = pubspecLockDescription(p2)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePubspecLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
|
||||
|
||||
@@ -79,6 +79,19 @@ func TestParsePubspecLock(t *testing.T) {
|
||||
Version: "1.6.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "flutter",
|
||||
Version: "0.0.0",
|
||||
PURL: "pkg:pub/flutter@0.0.0",
|
||||
Locations: fixtureLocationSet,
|
||||
Language: pkg.Dart,
|
||||
Type: pkg.DartPubPkg,
|
||||
MetadataType: pkg.DartPubMetadataType,
|
||||
Metadata: pkg.DartPubMetadata{
|
||||
Name: "flutter",
|
||||
Version: "0.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "key_binder",
|
||||
Version: "1.11.20",
|
||||
|
||||
@@ -36,6 +36,11 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
key_binder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -9,81 +9,151 @@ import (
|
||||
)
|
||||
|
||||
func TestDpkgCataloger(t *testing.T) {
|
||||
licenseLocation := file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
|
||||
expected := []pkg.Package{
|
||||
tests := []struct {
|
||||
name string
|
||||
expected []pkg.Package
|
||||
}{
|
||||
{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL-1", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("GPL-2", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation),
|
||||
),
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"),
|
||||
file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "libpam-runtime",
|
||||
Source: "pam",
|
||||
Version: "1.1.8-3.6",
|
||||
Architecture: "all",
|
||||
Maintainer: "Steve Langasek <vorlon@debian.org>",
|
||||
InstalledSize: 1016,
|
||||
Description: `Runtime support for the PAM library
|
||||
name: "image-dpkg",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL-1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")),
|
||||
),
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"),
|
||||
file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "libpam-runtime",
|
||||
Source: "pam",
|
||||
Version: "1.1.8-3.6",
|
||||
Architecture: "all",
|
||||
Maintainer: "Steve Langasek <vorlon@debian.org>",
|
||||
InstalledSize: 1016,
|
||||
Description: `Runtime support for the PAM library
|
||||
Contains configuration files and directories required for
|
||||
authentication to work on Debian systems. This package is required
|
||||
on almost all installations.`,
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/pam.conf",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "87fc76f18e98ee7d3848f6b81b3391e5",
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{
|
||||
Path: "/etc/pam.conf",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "87fc76f18e98ee7d3848f6b81b3391e5",
|
||||
},
|
||||
IsConfigFile: true,
|
||||
},
|
||||
{
|
||||
Path: "/etc/pam.d/other",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "31aa7f2181889ffb00b87df4126d1701",
|
||||
},
|
||||
IsConfigFile: true,
|
||||
},
|
||||
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "55f905631797551d4d936a34c7e73474",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "cede84bda30d2380217f97753c8ccf3a",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/changelog.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "f3c9dafa6da7992c47328b4464f6d122",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/copyright", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "a4fae96070439a5209a62ae5b8017ab2",
|
||||
}},
|
||||
},
|
||||
IsConfigFile: true,
|
||||
},
|
||||
{
|
||||
Path: "/etc/pam.d/other",
|
||||
Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "31aa7f2181889ffb00b87df4126d1701",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "image-distroless-deb",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "libsqlite3-0",
|
||||
Version: "3.34.1-3",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("public-domain", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2+", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
pkg.NewLicenseFromLocations("GPL-2", file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright")),
|
||||
),
|
||||
Locations: file.NewLocationSet(
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0", "/var/lib/dpkg/status.d/libsqlite3-0"),
|
||||
file.NewVirtualLocation("/var/lib/dpkg/status.d/libsqlite3-0.md5sums", "/var/lib/dpkg/status.d/libsqlite3-0.md5sums"),
|
||||
file.NewVirtualLocation("/usr/share/doc/libsqlite3-0/copyright", "/usr/share/doc/libsqlite3-0/copyright"),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
Package: "libsqlite3-0",
|
||||
Source: "sqlite3",
|
||||
Version: "3.34.1-3",
|
||||
Architecture: "arm64",
|
||||
Maintainer: "Laszlo Boszormenyi (GCS) <gcs@debian.org>",
|
||||
InstalledSize: 1490,
|
||||
Description: `SQLite 3 shared library
|
||||
SQLite is a C library that implements an SQL database engine.
|
||||
Programs that link with the SQLite library can have SQL database
|
||||
access without running a separate RDBMS process.`,
|
||||
Files: []pkg.DpkgFileRecord{
|
||||
{Path: "/usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "e11d70c96979a1328ae4e7e50542782b",
|
||||
}},
|
||||
{Path: "/usr/share/doc/libsqlite3-0/README.Debian", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "9d8facc2fa9d2df52f1c7cb4e5fa4741",
|
||||
}},
|
||||
{Path: "/usr/share/doc/libsqlite3-0/changelog.Debian.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "a58942e742f5056be0595e6ba69a323f",
|
||||
}},
|
||||
{Path: "/usr/share/doc/libsqlite3-0/changelog.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "52317be84c3ca44b7888c6921131e37d",
|
||||
}},
|
||||
{Path: "/usr/share/doc/libsqlite3-0/changelog.html.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "a856310354e6c8768e85b39ae838dd0a",
|
||||
}},
|
||||
{Path: "/usr/share/doc/libsqlite3-0/copyright", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "be64db3e095486e5e105652c51199358",
|
||||
}},
|
||||
},
|
||||
IsConfigFile: true,
|
||||
},
|
||||
{Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "55f905631797551d4d936a34c7e73474",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "cede84bda30d2380217f97753c8ccf3a",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/changelog.gz", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "f3c9dafa6da7992c47328b4464f6d122",
|
||||
}},
|
||||
{Path: "/usr/share/doc/zlib1g/copyright", Digest: &file.Digest{
|
||||
Algorithm: "md5",
|
||||
Value: "a4fae96070439a5209a62ae5b8017ab2",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewDpkgdbCataloger()
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, "image-dpkg").
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(expected, nil).
|
||||
TestCataloger(t, c)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := NewDpkgdbCataloger()
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, tt.name).
|
||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||
Expects(tt.expected, nil).
|
||||
TestCataloger(t, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCataloger_Globs(t *testing.T) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -176,16 +177,24 @@ func fetchMd5Contents(resolver file.Resolver, dbLocation file.Location, m pkg.Dp
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parentPath := filepath.Dir(dbLocation.RealPath)
|
||||
// for typical debian-base distributions, the installed package info is at /var/lib/dpkg/status
|
||||
// and the md5sum information is under /var/lib/dpkg/info/; however, for distroless the installed
|
||||
// package info is across multiple files under /var/lib/dpkg/status.d/ and the md5sums are contained in
|
||||
// the same directory
|
||||
searchPath := filepath.Dir(dbLocation.RealPath)
|
||||
|
||||
if !strings.HasSuffix(searchPath, "status.d") {
|
||||
searchPath = path.Join(searchPath, "info")
|
||||
}
|
||||
|
||||
// look for /var/lib/dpkg/info/NAME:ARCH.md5sums
|
||||
name := md5Key(m)
|
||||
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", name+md5sumsExt))
|
||||
location := resolver.RelativeFileByPath(dbLocation, path.Join(searchPath, name+md5sumsExt))
|
||||
|
||||
if location == nil {
|
||||
// the most specific key did not work, fallback to just the name
|
||||
// look for /var/lib/dpkg/info/NAME.md5sums
|
||||
location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", m.Package+md5sumsExt))
|
||||
location = resolver.RelativeFileByPath(dbLocation, path.Join(searchPath, m.Package+md5sumsExt))
|
||||
}
|
||||
|
||||
if location == nil {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
FROM scratch
|
||||
COPY . .
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: sqlite3
|
||||
Source: https://www.sqlite.org/cgi/src/dir?ci=trunk
|
||||
|
||||
Files: *
|
||||
Copyright: D. Richard Hipp <drh@hwaci.com>
|
||||
License: public-domain
|
||||
The files listed have been put on the public domain by the sqlite3
|
||||
contributors.
|
||||
|
||||
Files: debian/*
|
||||
Copyright: 2006- Laszlo Boszormenyi (GCS) <gcs@debian.org>,
|
||||
2005-2006 Tomas Fasth <tomfa@debian.org>,
|
||||
2001-2005 Andreas Rottmann <rotty@debian.org>
|
||||
License: GPL-2+
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License version 2 as published
|
||||
by the Free Software Foundation.
|
||||
.
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.
|
||||
.
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this package; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
.
|
||||
On Debian systems, the full text of the GNU General Public License
|
||||
version 2 can be found in the file `/usr/share/common-licenses/GPL-2'.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
Package: libsqlite3-0
|
||||
Source: sqlite3
|
||||
Version: 3.34.1-3
|
||||
Architecture: arm64
|
||||
Maintainer: Laszlo Boszormenyi (GCS) <gcs@debian.org>
|
||||
Installed-Size: 1490
|
||||
Depends: libc6 (>= 2.29)
|
||||
Breaks: python-migrate (<< 0.11.0-4~), python3-migrate (<< 0.11.0-4~)
|
||||
Section: libs
|
||||
Priority: optional
|
||||
Multi-Arch: same
|
||||
Homepage: https://www.sqlite.org/
|
||||
Description: SQLite 3 shared library
|
||||
SQLite is a C library that implements an SQL database engine.
|
||||
Programs that link with the SQLite library can have SQL database
|
||||
access without running a separate RDBMS process.
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
e11d70c96979a1328ae4e7e50542782b usr/lib/aarch64-linux-gnu/libsqlite3.so.0.8.6
|
||||
9d8facc2fa9d2df52f1c7cb4e5fa4741 usr/share/doc/libsqlite3-0/README.Debian
|
||||
a58942e742f5056be0595e6ba69a323f usr/share/doc/libsqlite3-0/changelog.Debian.gz
|
||||
52317be84c3ca44b7888c6921131e37d usr/share/doc/libsqlite3-0/changelog.gz
|
||||
a856310354e6c8768e85b39ae838dd0a usr/share/doc/libsqlite3-0/changelog.html.gz
|
||||
be64db3e095486e5e105652c51199358 usr/share/doc/libsqlite3-0/copyright
|
||||
@@ -47,8 +47,8 @@ import (
|
||||
// my-http2-server-5 --> name="my-http2-server", version="5"
|
||||
// jetpack-build235-rc5 --> name="jetpack", version="build2.0-rc5"
|
||||
// ironman-r4-2009 --> name="ironman", version="r4-2009"
|
||||
var nameAndVersionPattern = regexp.MustCompile(`(?Ui)^(?P<name>(?:[[:alpha:]][[:word:].]*(?:\.[[:alpha:]][[:word:].]*)*-?)+)(?:-(?P<version>(\d.*|(build\d*.*)|(rc?\d+(?:^[[:alpha:]].*)?))))?$`)
|
||||
var secondaryVersionPattern = regexp.MustCompile(`(?:[._-](?P<version>(\d.*|(build\d*.*)|(rc?\d+(?:^[[:alpha:]].*)?))))?$`)
|
||||
var nameAndVersionPattern = regexp.MustCompile(`(?Ui)^(?P<name>(?:[[:alpha:]][[:word:].]*(?:\.[[:alpha:]][[:word:].]*)*-?)+)(?:-(?P<version>(\d.*|(build\d+.*)|(rc?\d+(?:^[[:alpha:]].*)?))))?$`)
|
||||
var secondaryVersionPattern = regexp.MustCompile(`(?:[._-](?P<version>(\d.*|(build\d+.*)|(rc?\d+(?:^[[:alpha:]].*)?))))?$`)
|
||||
|
||||
type archiveFilename struct {
|
||||
raw string
|
||||
|
||||
@@ -173,6 +173,13 @@ func TestExtractInfoFromJavaArchiveFilename(t *testing.T) {
|
||||
name: "jboss-saaj-api_1.4_spec",
|
||||
ty: pkg.JavaPkg,
|
||||
},
|
||||
{
|
||||
filename: "/usr/share/java/gradle/lib/gradle-build-cache-8.1.1.jar",
|
||||
version: "8.1.1",
|
||||
extension: "jar",
|
||||
name: "gradle-build-cache",
|
||||
ty: pkg.JavaPkg,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -68,8 +68,9 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject {
|
||||
func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...file.Location) pkg.Package {
|
||||
m := pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: resolveProperty(pom, dep.GroupID),
|
||||
Scope: resolveProperty(pom, dep.Scope),
|
||||
GroupID: resolveProperty(pom, dep.GroupID),
|
||||
ArtifactID: resolveProperty(pom, dep.ArtifactID),
|
||||
Scope: resolveProperty(pom, dep.Scope),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ func Test_parserPomXML(t *testing.T) {
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{GroupID: "com.joda"},
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "com.joda",
|
||||
ArtifactID: "joda-time",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,8 +43,9 @@ func Test_parserPomXML(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "junit",
|
||||
Scope: "test",
|
||||
GroupID: "junit",
|
||||
ArtifactID: "junit",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -75,7 +79,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"},
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.apache.commons",
|
||||
ArtifactID: "commons-lang3",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -87,8 +94,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.junit.jupiter",
|
||||
Scope: "test",
|
||||
GroupID: "org.junit.jupiter",
|
||||
ArtifactID: "junit-jupiter",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -101,8 +109,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.assertj",
|
||||
Scope: "test",
|
||||
GroupID: "org.assertj",
|
||||
ArtifactID: "assertj-core",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -115,8 +124,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "commons-io",
|
||||
Scope: "test",
|
||||
GroupID: "commons-io",
|
||||
ArtifactID: "commons-io",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -129,8 +139,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.mockito",
|
||||
Scope: "test",
|
||||
GroupID: "org.mockito",
|
||||
ArtifactID: "mockito-inline",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -143,8 +154,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.graalvm.js",
|
||||
Scope: "test",
|
||||
GroupID: "org.graalvm.js",
|
||||
ArtifactID: "js",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -157,8 +169,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.graalvm.js",
|
||||
Scope: "test",
|
||||
GroupID: "org.graalvm.js",
|
||||
ArtifactID: "js-scriptengine",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -171,8 +184,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.apache.commons",
|
||||
Scope: "test",
|
||||
GroupID: "org.apache.commons",
|
||||
ArtifactID: "commons-rng-simple",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -185,8 +199,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.openjdk.jmh",
|
||||
Scope: "test",
|
||||
GroupID: "org.openjdk.jmh",
|
||||
ArtifactID: "jmh-core",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -199,8 +214,9 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.openjdk.jmh",
|
||||
Scope: "test",
|
||||
GroupID: "org.openjdk.jmh",
|
||||
ArtifactID: "jmh-generator-annprocess",
|
||||
Scope: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
// multiWriter holds a list of child sbom.Writers to apply all Write and Close operations to
|
||||
type multiWriter struct {
|
||||
writers []Writer
|
||||
}
|
||||
|
||||
// WriterOption Format and path strings used to create sbom.Writer
|
||||
type WriterOption struct {
|
||||
Format Format
|
||||
Path string
|
||||
}
|
||||
|
||||
func NewWriterOption(f Format, p string) WriterOption {
|
||||
expandedPath, err := homedir.Expand(p)
|
||||
if err != nil {
|
||||
log.Warnf("could not expand given writer output path=%q: %w", p, err)
|
||||
// ignore errors
|
||||
expandedPath = p
|
||||
}
|
||||
return WriterOption{
|
||||
Format: f,
|
||||
Path: expandedPath,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWriter create all report writers from input options; if a file is not specified, os.Stdout is used
|
||||
func NewWriter(options ...WriterOption) (_ Writer, err error) {
|
||||
if len(options) == 0 {
|
||||
return nil, fmt.Errorf("no output options provided")
|
||||
}
|
||||
|
||||
out := &multiWriter{}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// close any previously opened files; we can't really recover from any errors
|
||||
if err := out.Close(); err != nil {
|
||||
log.Warnf("unable to close sbom writers: %+v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for _, option := range options {
|
||||
switch len(option.Path) {
|
||||
case 0:
|
||||
out.writers = append(out.writers, &streamWriter{
|
||||
format: option.Format,
|
||||
out: os.Stdout,
|
||||
})
|
||||
default:
|
||||
// create any missing subdirectories
|
||||
dir := path.Dir(option.Path)
|
||||
if dir != "" {
|
||||
s, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(dir, 0755) // maybe should be os.ModePerm ?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !s.IsDir() {
|
||||
return nil, fmt.Errorf("output path does not contain a valid directory: %s", option.Path)
|
||||
}
|
||||
}
|
||||
fileOut, err := os.OpenFile(option.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create report file: %w", err)
|
||||
}
|
||||
out.writers = append(out.writers, &streamWriter{
|
||||
format: option.Format,
|
||||
out: fileOut,
|
||||
close: fileOut.Close,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Write writes the SBOM to all writers
|
||||
func (m *multiWriter) Write(s SBOM) (errs error) {
|
||||
for _, w := range m.writers {
|
||||
err := w.Write(s)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Bytes returns the bytes of the SBOM that would be written
|
||||
func (m *multiWriter) Bytes(s SBOM) (bytes []byte, err error) {
|
||||
for _, w := range m.writers {
|
||||
b, err := w.Bytes(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes = append(bytes, b...)
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// Close closes all writers
|
||||
func (m *multiWriter) Close() (errs error) {
|
||||
for _, w := range m.writers {
|
||||
err := w.Close()
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func dummyEncoder(io.Writer, SBOM) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dummyFormat(name string) Format {
|
||||
return NewFormat(AnyVersion, dummyEncoder, nil, nil, FormatID(name))
|
||||
}
|
||||
|
||||
type writerConfig struct {
|
||||
format string
|
||||
file string
|
||||
}
|
||||
|
||||
func TestOutputWriter(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
|
||||
testName := func(options []WriterOption, err bool) string {
|
||||
var out []string
|
||||
for _, opt := range options {
|
||||
out = append(out, string(opt.Format.ID())+"="+opt.Path)
|
||||
}
|
||||
errs := ""
|
||||
if err {
|
||||
errs = "(err)"
|
||||
}
|
||||
return strings.Join(out, ", ") + errs
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
outputs []WriterOption
|
||||
err bool
|
||||
expected []writerConfig
|
||||
}{
|
||||
{
|
||||
outputs: []WriterOption{},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
outputs: []WriterOption{
|
||||
{
|
||||
Format: dummyFormat("table"),
|
||||
Path: "",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "table",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []WriterOption{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []WriterOption{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
Path: "test-2.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
file: "test-2.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []WriterOption{
|
||||
{
|
||||
Format: dummyFormat("json"),
|
||||
Path: "test-3/1.json",
|
||||
},
|
||||
{
|
||||
Format: dummyFormat("spdx-json"),
|
||||
Path: "test-3/2.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "json",
|
||||
file: "test-3/1.json",
|
||||
},
|
||||
{
|
||||
format: "spdx-json",
|
||||
file: "test-3/2.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
outputs: []WriterOption{
|
||||
{
|
||||
Format: dummyFormat("text"),
|
||||
},
|
||||
{
|
||||
Format: dummyFormat("spdx-json"),
|
||||
Path: "test-4.json",
|
||||
},
|
||||
},
|
||||
expected: []writerConfig{
|
||||
{
|
||||
format: "text",
|
||||
},
|
||||
{
|
||||
format: "spdx-json",
|
||||
file: "test-4.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(testName(test.outputs, test.err), func(t *testing.T) {
|
||||
outputs := test.outputs
|
||||
for i := range outputs {
|
||||
if outputs[i].Path != "" {
|
||||
outputs[i].Path = tmp + outputs[i].Path
|
||||
}
|
||||
}
|
||||
|
||||
writer, err := NewWriter(outputs...)
|
||||
|
||||
if test.err {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
mw := writer.(*multiWriter)
|
||||
|
||||
assert.Len(t, mw.writers, len(test.expected))
|
||||
|
||||
for i, e := range test.expected {
|
||||
w := mw.writers[i].(*streamWriter)
|
||||
|
||||
assert.Equal(t, string(w.format.ID()), e.format)
|
||||
|
||||
if e.file != "" {
|
||||
assert.FileExists(t, tmp+e.file)
|
||||
}
|
||||
|
||||
if e.file != "" {
|
||||
assert.NotNil(t, w.out)
|
||||
assert.NotNil(t, w.close)
|
||||
} else {
|
||||
assert.NotNil(t, w.out)
|
||||
assert.Nil(t, w.close)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWriterOption(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "expand home dir",
|
||||
path: "~/place.txt",
|
||||
expected: filepath.Join(homedir.Get(), "place.txt"),
|
||||
},
|
||||
{
|
||||
name: "passthrough other paths",
|
||||
path: "/other/place.txt",
|
||||
expected: "/other/place.txt",
|
||||
},
|
||||
{
|
||||
name: "no path",
|
||||
path: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := NewWriterOption(dummyFormat("table"), tt.path)
|
||||
assert.Equal(t, tt.expected, o.Path)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package sbom
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// streamWriter implements sbom.Writer for a given format and io.Writer, also providing a close function for cleanup
|
||||
type streamWriter struct {
|
||||
format Format
|
||||
out io.Writer
|
||||
close func() error
|
||||
}
|
||||
|
||||
// Write the provided SBOM to the data stream
|
||||
func (w *streamWriter) Write(s SBOM) error {
|
||||
return w.format.Encode(w.out, s)
|
||||
}
|
||||
|
||||
// Bytes returns the bytes of the SBOM that would be written
|
||||
func (w *streamWriter) Bytes(s SBOM) ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
err := w.format.Encode(&buffer, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// Close any resources, such as open files
|
||||
func (w *streamWriter) Close() error {
|
||||
if w.close != nil {
|
||||
return w.close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+1
-11
@@ -1,16 +1,6 @@
|
||||
package sbom
|
||||
|
||||
import "io"
|
||||
|
||||
// Writer an interface to write SBOMs
|
||||
// Writer an interface to write SBOMs to a destination
|
||||
type Writer interface {
|
||||
// Write writes the provided SBOM
|
||||
Write(SBOM) error
|
||||
|
||||
// Bytes returns the bytes of the SBOM that would be written
|
||||
Bytes(SBOM) ([]byte, error)
|
||||
|
||||
// Closer a resource cleanup hook which will be called after SBOM
|
||||
// is written or if an error occurs before Write is called
|
||||
io.Closer
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user