Compare commits

...
21 Commits
Author SHA1 Message Date
anchore-actions-token-generator[bot]andkzantow 791d1f9552 chore(deps): update stereoscope to cd49355d934e9e09339e0b690398afe7bd9f63f1 (#1903)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-28 12:05:12 -04:00
anchore-actions-token-generator[bot]andspiffcs e5e97b5c4e chore(deps): update bootstrap tools to latest versions (#1902)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-06-28 12:04:39 -04:00
Weston Steimel 8219f8d55b fix: discover deb file relationships in distroless images (#1901)
Signed-off-by: Weston Steimel <weston.steimel@anchore.com>
2023-06-28 13:28:20 +01:00
Alex Goodman 026be3c0f1 add oss community board auto-add workflow (#1898)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-06-27 15:53:59 -04:00
anchore-actions-token-generator[bot]andkzantow 0d4f19043e chore(deps): update stereoscope to 8c7173ebcf69187d480d4d8b0c6cafaa7aef7024 (#1890)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-26 13:58:44 -04:00
anchore-actions-token-generator[bot]andspiffcs 38b47e484c chore(deps): update bootstrap tools to latest versions (#1894)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: spiffcs <spiffcs@users.noreply.github.com>
2023-06-26 13:58:17 -04:00
Stephane Rufer 7943c73d3f fix: add support for Dart SDK package dependencies (#1891)
Signed-off-by: Stephane Rufer <1128559+rufman@users.noreply.github.com>
2023-06-23 12:40:46 -04:00
Alex Goodman 25ce245c03 Simplify the SBOM writer interface (#1892)
* remove sbom.writer bytes call and consolidate helpers to options pkg

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* dont close stdout

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove close operation from multiwriter

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2023-06-23 11:21:22 -04:00
Dan Luhring 7de7a7990a fix: improve version detection in Java archive name parsing (#1889)
Signed-off-by: Dan Luhring <dluhring@chainguard.dev>
2023-06-22 18:42:10 +00:00
Keith Zantow f79cb9587f fix: only output valid cyclonedx license choices (#1879)
* fix: only output valid cyclonedx license choices

Signed-off-by: Keith Zantow <kzantow@gmail.com>

* chore: update tests

Signed-off-by: Keith Zantow <kzantow@gmail.com>

* chore: return nil for emtpty cdx license list

Signed-off-by: Keith Zantow <kzantow@gmail.com>

---------

Signed-off-by: Keith Zantow <kzantow@gmail.com>
2023-06-22 12:05:38 -04:00
Tim Gerla c27d5b11d4 docs: clarify reasoning of default catalogers for images or directories (#1887)
Add some explanation around why there are different default sets of catalogers for image scans versus directory scans. Hopefully clarify questions related to #1776.

Signed-off-by: Timothy Gerla <tim@gerla.net>
2023-06-20 19:47:50 +00:00
William Murphy 5d54e6e847 Configure chronicle to pre-1.0 mode (#1886)
Track a chronicle config file that causes chronicle to bump minor
version instead of major version in response to the "breaking-change"
label for pre-1.0 releases.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-06-20 16:08:35 +00:00
Keith Zantow 631d50d038 chore: update SPDX license list to 3.21 (#1885) 2023-06-20 15:47:02 +00:00
anchore-actions-token-generator[bot] 269006bf04 chore(deps): update bootstrap tools to latest versions (#1880) 2023-06-20 10:22:18 -04:00
William Murphy e2ed89f700 Pad artifact IDs (#1882)
Otherwise the hash can sometimes be short if it results in a low uint64.

Signed-off-by: Will Murphy <will.murphy@anchore.com>
2023-06-16 13:26:18 -04:00
dependabot[bot] badb957888 chore(deps): bump golang.org/x/mod from 0.10.0 to 0.11.0 (#1878)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/mod/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-15 14:10:11 -04:00
dependabot[bot] a1bba36d51 chore(deps): bump modernc.org/sqlite from 1.23.0 to 1.23.1 (#1874)
Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.23.0 to 1.23.1.
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.23.0...v1.23.1)

---
updated-dependencies:
- dependency-name: modernc.org/sqlite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 11:45:39 -04:00
anchore-actions-token-generator[bot]andkzantow c019cd51da chore(deps): update stereoscope to 5b5049bf4d3a99df9a2b1c31d5d52ddff7b5cec2 (#1871)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: kzantow <kzantow@users.noreply.github.com>
2023-06-14 11:29:39 -04:00
dependabot[bot] 5406d8a366 chore(deps): bump golang.org/x/net from 0.10.0 to 0.11.0 (#1876)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.10.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-14 10:30:19 -04:00
James Neate 098c255a2d fix: pom properties not setting artifact id (#1870)
Signed-off-by: James Neate <jamesmneate@gmail.com>
2023-06-12 09:59:14 -04:00
dependabot[bot] 2c5d64ac9e chore(deps): bump github.com/spdx/tools-golang from 0.5.1 to 0.5.2 (#1868)
Bumps [github.com/spdx/tools-golang](https://github.com/spdx/tools-golang) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/spdx/tools-golang/releases)
- [Changelog](https://github.com/spdx/tools-golang/blob/main/RELEASE-NOTES.md)
- [Commits](https://github.com/spdx/tools-golang/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: github.com/spdx/tools-golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-08 17:01:19 -04:00
35 changed files with 860 additions and 702 deletions
+1
View File
@@ -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 }}
+2 -2
View File
@@ -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
+6
View File
@@ -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
View File
@@ -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
+1 -7
View File
@@ -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]
+135 -7
View File
@@ -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
}
+199 -2
View File
@@ -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)
})
}
}
+1 -8
View File
@@ -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)
+1 -10
View File
@@ -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)
+9 -9
View File
@@ -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 (
+18 -18
View File
@@ -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=
+44 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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)
+18 -1
View File
@@ -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:
+132 -62
View File
@@ -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) {
+12 -3
View File
@@ -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 . .
@@ -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'.
@@ -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.
@@ -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
+2 -2
View File
@@ -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 {
+3 -2
View File
@@ -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),
},
}
+38 -22
View File
@@ -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",
},
},
},
-123
View File
@@ -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
}
-204
View File
@@ -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)
})
}
}
-36
View File
@@ -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
View File
@@ -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
}