mirror of
https://github.com/anchore/syft.git
synced 2026-10-14 15:17:18 +02:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e75fcf49d | ||
|
|
5c8dd4c3a7 | ||
|
|
f5a6b5a02f | ||
|
|
c379d21e9a | ||
|
|
a50a0f77d2 | ||
|
|
ea4a6747eb | ||
|
|
1cfc4c7387 | ||
|
|
4d0da703bf | ||
|
|
4ee6be3777 | ||
|
|
5d44e49d2f | ||
|
|
5dd3b127b0 | ||
|
|
ce4b31757a | ||
|
|
c08b0990ca | ||
|
|
8ee209a5ae | ||
|
|
4712246897 | ||
|
|
ebeb768f59 | ||
|
|
d4733fac1d | ||
|
|
34774a0e10 | ||
|
|
1c582f0aa5 |
+1
-1
@@ -90,7 +90,7 @@ tools:
|
||||
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.39.1
|
||||
want: v2.39.2
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
+77
-1
@@ -1,2 +1,78 @@
|
||||
enforce-v0: true # don't make breaking-change label bump major version before 1.0.
|
||||
title: ""
|
||||
title: ""
|
||||
|
||||
github:
|
||||
host: github.com
|
||||
include-issue-pr-authors: true
|
||||
include-issue-prs: true
|
||||
include-issues-not-planned: false
|
||||
include-prs: true
|
||||
include-issues: true
|
||||
include-unlabeled-issues: true
|
||||
include-unlabeled-prs: true
|
||||
issues-require-linked-prs: false
|
||||
consider-pr-merge-commits: true
|
||||
|
||||
exclude-labels:
|
||||
- duplicate
|
||||
- question
|
||||
- invalid
|
||||
- wontfix
|
||||
- wont-fix
|
||||
- release-ignore
|
||||
- changelog-ignore
|
||||
- ignore
|
||||
|
||||
changes:
|
||||
|
||||
- name: security-fixes
|
||||
title: Security Fixes
|
||||
semver-field: patch
|
||||
labels:
|
||||
- security
|
||||
- vulnerability
|
||||
|
||||
- name: added-feature
|
||||
title: Added Features
|
||||
semver-field: minor
|
||||
labels:
|
||||
- enhancement
|
||||
- feature
|
||||
- minor
|
||||
|
||||
- name: bug-fix
|
||||
title: Bug Fixes
|
||||
semver-field: patch
|
||||
labels:
|
||||
- bug
|
||||
- fix
|
||||
- bug-fix
|
||||
- patch
|
||||
|
||||
- name: breaking-feature
|
||||
title: Breaking Changes
|
||||
semver-field: major
|
||||
labels:
|
||||
- breaking
|
||||
- backwards-incompatible
|
||||
- breaking-change
|
||||
- breaking-feature
|
||||
- major
|
||||
- detected-breaking-change
|
||||
|
||||
- name: removed-feature
|
||||
title: Removed Features
|
||||
semver-field: major
|
||||
labels:
|
||||
- removed
|
||||
|
||||
- name: deprecated-feature
|
||||
title: Deprecated Features
|
||||
semver-field: minor
|
||||
labels:
|
||||
- deprecated
|
||||
|
||||
- name: unknown
|
||||
title: Additional Changes
|
||||
semver-field: ""
|
||||
labels: []
|
||||
|
||||
@@ -8,6 +8,12 @@ import re
|
||||
|
||||
DRY_RUN = False
|
||||
|
||||
JSON_SCHEMA_LABEL = "json-schema"
|
||||
|
||||
# note: we can't use "breaking-change" as the label since that might be applied manually by a user. This is a
|
||||
# distinct label that we can use to indicate that the label was applied (or removed) by automation.
|
||||
BREAKING_CHANGE_LABEL = "detected-breaking-change"
|
||||
|
||||
|
||||
def main(changed_files: str | None = None, merge_base_schema_files: str | None = None):
|
||||
global DRY_RUN
|
||||
@@ -67,17 +73,18 @@ def main(changed_files: str | None = None, merge_base_schema_files: str | None =
|
||||
# if there is a new or modified schema, we should add the "json-schema" label to the PR...
|
||||
if new_schema_files or removed_or_modified_schema_files:
|
||||
print("\nAdding json-schema label...")
|
||||
add_label(pr_number, "json-schema")
|
||||
add_label(pr_number, JSON_SCHEMA_LABEL)
|
||||
|
||||
else:
|
||||
remove_label(pr_number, "json-schema")
|
||||
remove_label(pr_number, JSON_SCHEMA_LABEL)
|
||||
|
||||
# new schema files should be scrutinized, comparing the latest and added versions to see if it's a breaking
|
||||
# change (major version bump). Warn about it on the PR via adding a breaking-change label...
|
||||
if is_breaking_change(new_schema_files, og_json_schema_files[-1]):
|
||||
print("\nBreaking change detected...")
|
||||
add_label(pr_number, "breaking-change")
|
||||
add_label(pr_number, BREAKING_CHANGE_LABEL)
|
||||
else:
|
||||
remove_label(pr_number, "breaking-change")
|
||||
remove_label(pr_number, BREAKING_CHANGE_LABEL)
|
||||
|
||||
# modifying an existing schema could be a breaking change, we should warn about it on the PR via a comment...
|
||||
# removing schema files should never be allowed, we should warn about it on the PR via a comment...
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 #v2.22.8
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 #v2.22.8
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@66b90a5db151a8042fa97405c6cf843bbe433f7b #v2.22.7
|
||||
uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 #v2.22.8
|
||||
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.TOOLBOX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.TOOLBOX_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- uses: anchore/sbom-action@78fc58e266e87a38d4194b2137a3d4e9bcaf7ca1 #v0.14.3
|
||||
- uses: anchore/sbom-action@fd74a6fb98a204a1ad35bbfae0122c1a302ff88b #v0.15.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -91,5 +91,8 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
|
||||
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
|
||||
)
|
||||
|
||||
// explicitly set Cobra output to the real stdout to write things like errors and help
|
||||
rootCmd.SetOut(out)
|
||||
|
||||
return app, rootCmd
|
||||
}
|
||||
|
||||
+128
-116
@@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -11,15 +12,14 @@ import (
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/format"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/format/spdxjson"
|
||||
"github.com/anchore/syft/syft/format/spdxtagvalue"
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
`
|
||||
attestSchemeHelp = "\n " + schemeHelpHeader + "\n" + imageSchemeHelp
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
cosignBinName = "cosign"
|
||||
)
|
||||
|
||||
type attestOptions struct {
|
||||
@@ -46,24 +47,7 @@ type attestOptions struct {
|
||||
func Attest(app clio.Application) *cobra.Command {
|
||||
id := app.ID()
|
||||
|
||||
opts := &attestOptions{
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
Output: options.Output{
|
||||
AllowMultipleOutputs: false,
|
||||
AllowableOptions: []string{
|
||||
string(syftjson.ID),
|
||||
string(cyclonedxjson.ID),
|
||||
string(spdxjson.ID),
|
||||
string(spdxtagvalue.ID),
|
||||
},
|
||||
Outputs: []string{syftjson.ID.String()},
|
||||
OutputFile: options.OutputFile{ // nolint:staticcheck
|
||||
Enabled: false, // explicitly not allowed
|
||||
},
|
||||
Format: options.DefaultFormat(),
|
||||
},
|
||||
Catalog: options.DefaultCatalog(),
|
||||
}
|
||||
opts := defaultAttestOptions()
|
||||
|
||||
// template format explicitly not allowed
|
||||
opts.Format.Template.Enabled = false
|
||||
@@ -82,83 +66,96 @@ func Attest(app clio.Application) *cobra.Command {
|
||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||
defer restoreStdout()
|
||||
|
||||
return runAttest(id, opts, args[0])
|
||||
return runAttest(id, &opts, args[0])
|
||||
},
|
||||
}, opts)
|
||||
}, &opts)
|
||||
}
|
||||
|
||||
func defaultAttestOptions() attestOptions {
|
||||
return attestOptions{
|
||||
Output: defaultAttestOutputOptions(),
|
||||
UpdateCheck: options.DefaultUpdateCheck(),
|
||||
Catalog: options.DefaultCatalog(),
|
||||
}
|
||||
}
|
||||
|
||||
func defaultAttestOutputOptions() options.Output {
|
||||
return options.Output{
|
||||
AllowMultipleOutputs: false,
|
||||
AllowToFile: false,
|
||||
AllowableOptions: []string{
|
||||
string(syftjson.ID),
|
||||
string(cyclonedxjson.ID),
|
||||
string(spdxjson.ID),
|
||||
string(spdxtagvalue.ID),
|
||||
},
|
||||
Outputs: []string{syftjson.ID.String()},
|
||||
OutputFile: options.OutputFile{ // nolint:staticcheck
|
||||
Enabled: false, // explicitly not allowed
|
||||
},
|
||||
Format: options.DefaultFormat(),
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func runAttest(id clio.Identification, opts *attestOptions, userInput string) error {
|
||||
_, err := exec.LookPath("cosign")
|
||||
if err != nil {
|
||||
// when cosign is not installed the error will be rendered like so:
|
||||
// 2023/06/30 08:31:52 error during command execution: 'syft attest' requires cosign to be installed: exec: "cosign": executable file not found in $PATH
|
||||
return fmt.Errorf("'syft attest' requires cosign to be installed: %w", err)
|
||||
}
|
||||
|
||||
s, err := buildSBOM(id, &opts.Catalog, userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to build SBOM: %w", err)
|
||||
// TODO: what other validation here besides binary name?
|
||||
if !commandExists(cosignBinName) {
|
||||
return fmt.Errorf("'syft attest' requires cosign to be installed, however it does not appear to be on PATH")
|
||||
}
|
||||
|
||||
// this is the file that will contain the SBOM being attested
|
||||
f, err := os.CreateTemp("", "syft-attest-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp file: %w", err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
writer, err := opts.SBOMWriter()
|
||||
s, err := generateSBOMForAttestation(id, &opts.Catalog, userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create SBOM writer: %w", err)
|
||||
return fmt.Errorf("unable to build SBOM: %w", err)
|
||||
}
|
||||
|
||||
if err := writer.Write(*s); err != nil {
|
||||
return fmt.Errorf("unable to write SBOM to temp file: %w", err)
|
||||
if err = writeSBOMToFormattedFile(s, f, opts); err != nil {
|
||||
return fmt.Errorf("unable to write SBOM to file: %w", err)
|
||||
}
|
||||
|
||||
// TODO: what other validation here besides binary name?
|
||||
cmd := "cosign"
|
||||
if !commandExists(cmd) {
|
||||
return fmt.Errorf("unable to find cosign in PATH; make sure you have it installed")
|
||||
if err = createAttestation(f.Name(), opts, userInput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputNames := opts.OutputNameSet()
|
||||
var outputName string
|
||||
switch outputNames.Size() {
|
||||
case 0:
|
||||
return fmt.Errorf("no output format specified")
|
||||
case 1:
|
||||
outputName = outputNames.List()[0]
|
||||
default:
|
||||
return fmt.Errorf("multiple output formats specified: %s", strings.Join(outputNames.List(), ", "))
|
||||
bus.Notify("Attestation has been created, please check your registry for the output or use the cosign command:")
|
||||
bus.Notify(fmt.Sprintf("cosign download attestation %s", userInput))
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeSBOMToFormattedFile(s *sbom.SBOM, sbomFile io.Writer, opts *attestOptions) error {
|
||||
if sbomFile == nil {
|
||||
return fmt.Errorf("no output file provided")
|
||||
}
|
||||
|
||||
// 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(outputName) {
|
||||
case "cyclonedx-json":
|
||||
predicateType = "cyclonedx"
|
||||
case "spdx-tag-value", "spdx-tv":
|
||||
predicateType = "spdx"
|
||||
case "spdx-json", "json":
|
||||
predicateType = "spdxjson"
|
||||
default:
|
||||
predicateType = "custom"
|
||||
encs, err := opts.Format.Encoders()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create encoders: %w", err)
|
||||
}
|
||||
|
||||
args := []string{"attest", userInput, "--predicate", f.Name(), "--type", predicateType}
|
||||
if opts.Attest.Key != "" {
|
||||
args = append(args, "--key", opts.Attest.Key.String())
|
||||
encoders := format.NewEncoderCollection(encs...)
|
||||
encoder := encoders.GetByString(opts.Outputs[0])
|
||||
if encoder == nil {
|
||||
return fmt.Errorf("unable to find encoder for %q", opts.Outputs[0])
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
if opts.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", opts.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
if err = encoder.Encode(sbomFile, *s); err != nil {
|
||||
return fmt.Errorf("unable to encode SBOM: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createAttestation(sbomFilepath string, opts *attestOptions, userInput string) error {
|
||||
execCmd, err := attestCommand(sbomFilepath, opts, userInput)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to craft attest command: %w", err)
|
||||
}
|
||||
|
||||
log.WithFields("cmd", strings.Join(execCmd.Args, " ")).Trace("creating attestation")
|
||||
@@ -201,59 +198,67 @@ func runAttest(id clio.Identification, opts *attestOptions, userInput string) er
|
||||
}
|
||||
|
||||
mon.SetCompleted()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildSBOM(id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
|
||||
cfg := source.DetectConfig{
|
||||
DefaultImageSource: opts.DefaultImagePullSource,
|
||||
func attestCommand(sbomFilepath string, opts *attestOptions, userInput string) (*exec.Cmd, error) {
|
||||
outputNames := opts.OutputNameSet()
|
||||
var outputName string
|
||||
switch outputNames.Size() {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("no output format specified")
|
||||
case 1:
|
||||
outputName = outputNames.List()[0]
|
||||
default:
|
||||
return nil, fmt.Errorf("multiple output formats specified: %s", strings.Join(outputNames.List(), ", "))
|
||||
}
|
||||
detection, err := source.Detect(userInput, cfg)
|
||||
|
||||
args := []string{"attest", userInput, "--predicate", sbomFilepath, "--type", predicateType(outputName), "-y"}
|
||||
if opts.Attest.Key != "" {
|
||||
args = append(args, "--key", opts.Attest.Key.String())
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cosignBinName, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
if opts.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", opts.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
return execCmd, nil
|
||||
}
|
||||
|
||||
func predicateType(outputName string) string {
|
||||
// Select Cosign predicate type based on defined output type
|
||||
// As orientation, check: https://github.com/sigstore/cosign/blob/main/pkg/cosign/attestation/attestation.go
|
||||
switch strings.ToLower(outputName) {
|
||||
case "cyclonedx-json":
|
||||
return "cyclonedx"
|
||||
case "spdx-tag-value", "spdx-tv":
|
||||
return "spdx"
|
||||
case "spdx-json", "json":
|
||||
return "spdxjson"
|
||||
default:
|
||||
return "custom"
|
||||
}
|
||||
}
|
||||
|
||||
func generateSBOMForAttestation(id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
|
||||
src, err := getSource(opts, userInput, onlyContainerImages)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deteremine source: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if detection.IsContainerImage() {
|
||||
return nil, fmt.Errorf("attestations are only supported for oci images at this time")
|
||||
}
|
||||
|
||||
var platform *image.Platform
|
||||
|
||||
if opts.Platform != "" {
|
||||
platform, err = image.NewPlatform(opts.Platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid platform: %w", err)
|
||||
defer func() {
|
||||
if src != nil {
|
||||
if err := src.Close(); err != nil {
|
||||
log.Tracef("unable to close source: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash: %w", err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(
|
||||
source.DetectionSourceConfig{
|
||||
Alias: source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
},
|
||||
RegistryOptions: opts.Registry.ToOptions(),
|
||||
Platform: platform,
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
},
|
||||
DigestAlgorithms: hashers,
|
||||
BasePath: opts.BasePath,
|
||||
},
|
||||
)
|
||||
|
||||
if src != nil {
|
||||
defer src.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
|
||||
}
|
||||
}()
|
||||
|
||||
s, err := generateSBOM(id, src, opts)
|
||||
if err != nil {
|
||||
@@ -267,6 +272,13 @@ func buildSBOM(id clio.Identification, opts *options.Catalog, userInput string)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func onlyContainerImages(d *source.Detection) error {
|
||||
if !d.IsContainerImage() {
|
||||
return fmt.Errorf("attestations are only supported for oci images at this time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_writeSBOMToFormattedFile(t *testing.T) {
|
||||
type args struct {
|
||||
s *sbom.SBOM
|
||||
opts *attestOptions
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantSbomFile string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
args: args{
|
||||
opts: &attestOptions{
|
||||
Output: func() options.Output {
|
||||
def := defaultAttestOutputOptions()
|
||||
def.Outputs = []string{"syft-json"}
|
||||
return def
|
||||
}(),
|
||||
},
|
||||
s: &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{},
|
||||
Relationships: nil,
|
||||
Source: source.Description{
|
||||
ID: "source-id",
|
||||
Name: "source-name",
|
||||
Version: "source-version",
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft-test",
|
||||
Version: "non-version",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantSbomFile: `{
|
||||
"artifacts": [],
|
||||
"artifactRelationships": [],
|
||||
"source": {
|
||||
"id": "source-id",
|
||||
"name": "source-name",
|
||||
"version": "source-version",
|
||||
"type": "",
|
||||
"metadata": null
|
||||
},
|
||||
"distro": {},
|
||||
"descriptor": {
|
||||
"name": "syft-test",
|
||||
"version": "non-version"
|
||||
},
|
||||
"schema": {}
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sbomFile := &bytes.Buffer{}
|
||||
|
||||
err := writeSBOMToFormattedFile(tt.args.s, sbomFile, tt.args.opts)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("writeSBOMToFormattedFile() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// redact the schema block
|
||||
re := regexp.MustCompile(`(?s)"schema":\W*\{.*?},?`)
|
||||
subject := re.ReplaceAllString(sbomFile.String(), `"schema":{}`)
|
||||
|
||||
assert.JSONEq(t, tt.wantSbomFile, subject)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_attestCommand(t *testing.T) {
|
||||
cmdPrefix := cosignBinName
|
||||
lp, err := exec.LookPath(cosignBinName)
|
||||
if err == nil {
|
||||
cmdPrefix = lp
|
||||
}
|
||||
|
||||
fullCmd := func(args string) string {
|
||||
return fmt.Sprintf("%s %s", cmdPrefix, args)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
sbomFilepath string
|
||||
opts attestOptions
|
||||
userInput string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCmd string
|
||||
wantEnvVars map[string]string
|
||||
notEnvVars []string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "with key and password",
|
||||
args: args{
|
||||
userInput: "myimage",
|
||||
sbomFilepath: "/tmp/sbom-filepath.json",
|
||||
opts: func() attestOptions {
|
||||
def := defaultAttestOptions()
|
||||
def.Outputs = []string{"syft-json"}
|
||||
def.Attest.Key = "key"
|
||||
def.Attest.Password = "password"
|
||||
return def
|
||||
}(),
|
||||
},
|
||||
wantCmd: fullCmd("attest myimage --predicate /tmp/sbom-filepath.json --type custom -y --key key"),
|
||||
wantEnvVars: map[string]string{
|
||||
"COSIGN_PASSWORD": "password",
|
||||
},
|
||||
notEnvVars: []string{
|
||||
"COSIGN_EXPERIMENTAL", // only for keyless
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keyless",
|
||||
args: args{
|
||||
userInput: "myimage",
|
||||
sbomFilepath: "/tmp/sbom-filepath.json",
|
||||
opts: func() attestOptions {
|
||||
def := defaultAttestOptions()
|
||||
def.Outputs = []string{"syft-json"}
|
||||
return def
|
||||
}(),
|
||||
},
|
||||
wantCmd: fullCmd("attest myimage --predicate /tmp/sbom-filepath.json --type custom -y"),
|
||||
wantEnvVars: map[string]string{
|
||||
"COSIGN_EXPERIMENTAL": "1",
|
||||
},
|
||||
notEnvVars: []string{
|
||||
"COSIGN_PASSWORD",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
got, err := attestCommand(tt.args.sbomFilepath, &tt.args.opts, tt.args.userInput)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, tt.wantCmd, got.String())
|
||||
|
||||
gotEnv := strset.New(got.Env...)
|
||||
|
||||
for k, v := range tt.wantEnvVars {
|
||||
assert.True(t, gotEnv.Has(fmt.Sprintf("%s=%s", k, v)))
|
||||
}
|
||||
|
||||
for _, k := range tt.notEnvVars {
|
||||
for _, env := range got.Env {
|
||||
fields := strings.Split(env, "=")
|
||||
if fields[0] == k {
|
||||
t.Errorf("attestCommand() unexpected environment variable %s", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_predicateType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "cyclonedx-json",
|
||||
want: "cyclonedx",
|
||||
},
|
||||
{
|
||||
name: "spdx-tag-value",
|
||||
want: "spdx",
|
||||
},
|
||||
{
|
||||
name: "spdx-tv",
|
||||
want: "spdx",
|
||||
},
|
||||
{
|
||||
name: "spdx-json",
|
||||
want: "spdxjson",
|
||||
},
|
||||
{
|
||||
name: "json",
|
||||
want: "spdxjson",
|
||||
},
|
||||
{
|
||||
name: "syft-json",
|
||||
want: "custom",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, predicateType(tt.name), "predicateType(%v)", tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_buildSBOMForAttestation(t *testing.T) {
|
||||
// note: this test is only meant to test that the filter function is wired
|
||||
// and not the correctness of the function in depth
|
||||
type args struct {
|
||||
id clio.Identification
|
||||
opts *options.Catalog
|
||||
userInput string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *sbom.SBOM
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "do not allow directory scans",
|
||||
args: args{
|
||||
opts: func() *options.Catalog {
|
||||
def := defaultAttestOptions()
|
||||
return &def.Catalog
|
||||
}(),
|
||||
userInput: "dir:/tmp/something",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
_, err := generateSBOMForAttestation(tt.args.id, tt.args.opts, tt.args.userInput)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -117,51 +117,10 @@ func runPackages(id clio.Identification, opts *packagesOptions, userInput string
|
||||
return err
|
||||
}
|
||||
|
||||
detection, err := source.Detect(
|
||||
userInput,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: opts.DefaultImagePullSource,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not deteremine source: %w", err)
|
||||
}
|
||||
|
||||
var platform *image.Platform
|
||||
|
||||
if opts.Platform != "" {
|
||||
platform, err = image.NewPlatform(opts.Platform)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid platform: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hash: %w", err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(
|
||||
source.DetectionSourceConfig{
|
||||
Alias: source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
},
|
||||
RegistryOptions: opts.Registry.ToOptions(),
|
||||
Platform: platform,
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
},
|
||||
DigestAlgorithms: hashers,
|
||||
BasePath: opts.BasePath,
|
||||
},
|
||||
)
|
||||
src, err := getSource(&opts.Catalog, userInput)
|
||||
|
||||
if err != nil {
|
||||
if userInput == "power-user" {
|
||||
bus.Notify("Note: the 'power-user' command has been removed.")
|
||||
}
|
||||
return fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -188,6 +147,63 @@ func runPackages(id clio.Identification, opts *packagesOptions, userInput string
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSource(opts *options.Catalog, userInput string, filters ...func(*source.Detection) error) (source.Source, error) {
|
||||
detection, err := source.Detect(
|
||||
userInput,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: opts.DefaultImagePullSource,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not deteremine source: %w", err)
|
||||
}
|
||||
|
||||
for _, filter := range filters {
|
||||
if err := filter(detection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var platform *image.Platform
|
||||
|
||||
if opts.Platform != "" {
|
||||
platform, err = image.NewPlatform(opts.Platform)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid platform: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash: %w", err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(
|
||||
source.DetectionSourceConfig{
|
||||
Alias: source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
},
|
||||
RegistryOptions: opts.Registry.ToOptions(),
|
||||
Platform: platform,
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
},
|
||||
DigestAlgorithms: hashers,
|
||||
BasePath: opts.BasePath,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if userInput == "power-user" {
|
||||
bus.Notify("Note: the 'power-user' command has been removed.")
|
||||
}
|
||||
return nil, fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func generateSBOM(id clio.Identification, src source.Source, opts *options.Catalog) (*sbom.SBOM, error) {
|
||||
tasks, err := eventloop.Tasks(opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
)
|
||||
|
||||
func Root(app clio.Application, packagesCmd *cobra.Command) *cobra.Command {
|
||||
@@ -21,6 +22,9 @@ func Root(app clio.Application, packagesCmd *cobra.Command) *cobra.Command {
|
||||
Example: packagesCmd.Example,
|
||||
PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
restoreStdout := ui.CaptureStdoutToTraceLog()
|
||||
defer restoreStdout()
|
||||
|
||||
return runPackages(id, opts, args[0])
|
||||
},
|
||||
}, opts)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/fangs"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
javaCataloger "github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
@@ -126,19 +127,24 @@ func (cfg Catalog) ToCatalogerConfig() cataloger.Config {
|
||||
},
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
Golang: golangCataloger.NewGoCatalogerOpts().
|
||||
Golang: golangCataloger.DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy),
|
||||
LinuxKernel: kernel.LinuxCatalogerConfig{
|
||||
LinuxKernel: kernel.LinuxKernelCatalogerConfig{
|
||||
CatalogModules: cfg.LinuxKernel.CatalogModules,
|
||||
},
|
||||
Java: javaCataloger.DefaultCatalogerOpts().
|
||||
Java: javaCataloger.DefaultArchiveCatalogerConfig().
|
||||
WithUseNetwork(cfg.Java.UseNetwork).
|
||||
WithMavenURL(cfg.Java.MavenURL).
|
||||
WithMaxParentRecursiveDepth(cfg.Java.MaxParentRecursiveDepth),
|
||||
WithMavenBaseURL(cfg.Java.MavenURL).
|
||||
WithArchiveTraversal(
|
||||
cataloging.ArchiveSearchConfig{
|
||||
IncludeIndexedArchives: cfg.Package.SearchIndexedArchives,
|
||||
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
||||
},
|
||||
cfg.Java.MaxParentRecursiveDepth),
|
||||
Python: pythonCataloger.CatalogerConfig{
|
||||
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
|
||||
},
|
||||
|
||||
@@ -32,8 +32,12 @@ func (o FormatCyclonedxJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
|
||||
}
|
||||
|
||||
func (o FormatCyclonedxJSON) buildConfig(version string) cyclonedxjson.EncoderConfig {
|
||||
var pretty bool
|
||||
if o.Pretty != nil {
|
||||
pretty = *o.Pretty
|
||||
}
|
||||
return cyclonedxjson.EncoderConfig{
|
||||
Version: version,
|
||||
Pretty: *o.Pretty,
|
||||
Pretty: pretty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,12 @@ func (o FormatCyclonedxXML) formatEncoders() ([]sbom.FormatEncoder, error) {
|
||||
}
|
||||
|
||||
func (o FormatCyclonedxXML) buildConfig(version string) cyclonedxxml.EncoderConfig {
|
||||
var pretty bool
|
||||
if o.Pretty != nil {
|
||||
pretty = *o.Pretty
|
||||
}
|
||||
return cyclonedxxml.EncoderConfig{
|
||||
Version: version,
|
||||
Pretty: *o.Pretty,
|
||||
Pretty: pretty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,12 @@ func (o FormatSPDXJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
|
||||
}
|
||||
|
||||
func (o FormatSPDXJSON) buildConfig(v string) spdxjson.EncoderConfig {
|
||||
var pretty bool
|
||||
if o.Pretty != nil {
|
||||
pretty = *o.Pretty
|
||||
}
|
||||
return spdxjson.EncoderConfig{
|
||||
Version: v,
|
||||
Pretty: *o.Pretty,
|
||||
Pretty: pretty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@ func (o FormatSyftJSON) formatEncoders() ([]sbom.FormatEncoder, error) {
|
||||
}
|
||||
|
||||
func (o FormatSyftJSON) buildConfig() syftjson.EncoderConfig {
|
||||
var pretty bool
|
||||
if o.Pretty != nil {
|
||||
pretty = *o.Pretty
|
||||
}
|
||||
return syftjson.EncoderConfig{
|
||||
Legacy: o.Legacy,
|
||||
Pretty: *o.Pretty,
|
||||
Pretty: pretty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ var _ interface {
|
||||
type Output struct {
|
||||
AllowableOptions []string `yaml:"-" json:"-" mapstructure:"-"`
|
||||
AllowMultipleOutputs bool `yaml:"-" json:"-" mapstructure:"-"`
|
||||
AllowToFile bool `yaml:"-" json:"-" mapstructure:"-"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputFile `yaml:",inline" json:"" mapstructure:",squash"`
|
||||
Format `yaml:"format" json:"format" mapstructure:"format"`
|
||||
@@ -38,6 +39,7 @@ type Output struct {
|
||||
func DefaultOutput() Output {
|
||||
return Output{
|
||||
AllowMultipleOutputs: true,
|
||||
AllowToFile: true,
|
||||
Outputs: []string{string(table.ID)},
|
||||
OutputFile: OutputFile{
|
||||
Enabled: true,
|
||||
@@ -86,6 +88,14 @@ func (o Output) SBOMWriter() (sbom.Writer, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !o.AllowToFile {
|
||||
for _, opt := range o.Outputs {
|
||||
if strings.Contains(opt, "=") {
|
||||
return nil, fmt.Errorf("file output is not allowed ('-o format=path' should be '-o format')")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return makeSBOMWriter(o.Outputs, o.File, encoders)
|
||||
}
|
||||
|
||||
|
||||
@@ -179,3 +179,25 @@ func Test_EncoderCollection_ByString_IDOnly_Defaults(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_OutputHonorsAllowFile(t *testing.T) {
|
||||
o := DefaultOutput()
|
||||
|
||||
t.Run("file is not allowed", func(t *testing.T) {
|
||||
o.AllowToFile = false
|
||||
o.Outputs = []string{"table=/tmp/somefile"}
|
||||
|
||||
w, err := o.SBOMWriter()
|
||||
assert.Nil(t, w)
|
||||
assert.ErrorContains(t, err, "file output is not allowed")
|
||||
})
|
||||
|
||||
t.Run("file is allowed", func(t *testing.T) {
|
||||
o.AllowToFile = true
|
||||
o.Outputs = []string{"table=/tmp/somefile"}
|
||||
|
||||
w, err := o.SBOMWriter()
|
||||
assert.NotNil(t, w)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/github/go-spdx/v2 v2.2.0
|
||||
github.com/gkampitakis/go-snaps v0.4.12
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-git/v5 v5.10.0
|
||||
github.com/go-git/go-git/v5 v5.10.1
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/go-containerregistry v0.16.1
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/spdx/tools-golang v0.5.3
|
||||
github.com/spf13/afero v1.10.0
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vbatts/go-mtree v0.5.3
|
||||
@@ -71,7 +71,7 @@ require (
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
golang.org/x/mod v0.14.0
|
||||
golang.org/x/net v0.18.0
|
||||
golang.org/x/net v0.19.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.27.0
|
||||
)
|
||||
@@ -87,7 +87,6 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/adrg/xdg v0.4.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
@@ -182,7 +181,7 @@ require (
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
@@ -208,17 +207,17 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.14.0 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
|
||||
google.golang.org/grpc v1.58.3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
|
||||
@@ -3,7 +3,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
@@ -16,7 +15,6 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
@@ -47,7 +45,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
@@ -86,8 +83,6 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -293,8 +288,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.10.0 h1:F0x3xXrAWmhwtzoCokU4IMPcBdncG+HAAqi9FcOOjbQ=
|
||||
github.com/go-git/go-git/v5 v5.10.0/go.mod h1:1FOZ/pQnqw24ghP2n7cunVl0ON55BsjPYvhWHvZGhoo=
|
||||
github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk=
|
||||
github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -391,7 +386,6 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
@@ -409,7 +403,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
@@ -518,8 +511,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -639,7 +630,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
@@ -707,8 +697,8 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
|
||||
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
|
||||
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
|
||||
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@@ -717,8 +707,8 @@ github.com/spdx/tools-golang v0.5.3 h1:ialnHeEYUC4+hkm5vJm4qz2x+oEJbS0mAMFrNXdQr
|
||||
github.com/spdx/tools-golang v0.5.3/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.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
|
||||
@@ -852,16 +842,14 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
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=
|
||||
@@ -952,8 +940,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
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=
|
||||
@@ -984,8 +972,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -1033,7 +1021,6 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -1041,7 +1028,6 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -1071,16 +1057,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
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=
|
||||
@@ -1098,8 +1084,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -1149,7 +1135,6 @@ golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
@@ -1240,9 +1225,7 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
@@ -1270,10 +1253,10 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6
|
||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb h1:XFBgcDwm7irdHTbz4Zk2h7Mh+eis4nfJEFQFYzJzuIA=
|
||||
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 h1:N3bU/SQDCDyD6R528GJ/PwW9KjYcJA3dgyH+MovAkIM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:KSqppvjFjtoCI+KGd4PELB0qLNxdJHRGqRI09mB6pQA=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1301,8 +1284,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package cataloging
|
||||
|
||||
type ArchiveSearchConfig struct {
|
||||
IncludeIndexedArchives bool `yaml:"include-indexed-archives" json:"include-indexed-archives" mapstructure:"include-indexed-archives"`
|
||||
IncludeUnindexedArchives bool `yaml:"include-unindexed-archives" json:"include-unindexed-archives" mapstructure:"include-unindexed-archives"`
|
||||
}
|
||||
@@ -27,9 +27,16 @@ func (i *Cataloger) Catalog(resolver file.Resolver, coordinates ...file.Coordina
|
||||
locations = func() <-chan file.Location {
|
||||
ch := make(chan file.Location)
|
||||
go func() {
|
||||
close(ch)
|
||||
defer close(ch)
|
||||
for _, c := range coordinates {
|
||||
ch <- file.NewLocationFromCoordinates(c)
|
||||
locs, err := resolver.FilesByPath(c.RealPath)
|
||||
if err != nil {
|
||||
log.Warn("unable to get file locations for path %q: %w", c.RealPath, err)
|
||||
continue
|
||||
}
|
||||
for _, loc := range locs {
|
||||
ch <- loc
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch
|
||||
|
||||
@@ -21,19 +21,13 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
c := NewCataloger()
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create source: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create resolver: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := c.Catalog(resolver)
|
||||
if err != nil {
|
||||
t.Fatalf("could not catalog: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
@@ -156,3 +150,67 @@ func TestFileMetadataCataloger(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFileMetadataCataloger_GivenCoordinates(t *testing.T) {
|
||||
testImage := "image-file-type-mix"
|
||||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
c := NewCataloger()
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
exists bool
|
||||
expected file.Metadata
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
path: "/file-1.txt",
|
||||
exists: true,
|
||||
expected: file.Metadata{
|
||||
FileInfo: stereoscopeFile.ManualInfo{
|
||||
NameValue: "file-1.txt",
|
||||
ModeValue: 0644,
|
||||
SizeValue: 7,
|
||||
},
|
||||
Path: "/file-1.txt",
|
||||
Type: stereoscopeFile.TypeRegular,
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
_, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
|
||||
require.NoError(t, err)
|
||||
|
||||
l := file.NewLocationFromImage(test.path, *ref.Reference, img)
|
||||
|
||||
// note: an important difference between this test and the previous is that this test is using a list
|
||||
// of specific coordinates to catalog
|
||||
actual, err := c.Catalog(resolver, l.Coordinates)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, actual, 1)
|
||||
|
||||
if _, ok := actual[l.Coordinates]; ok {
|
||||
// we're not interested in keeping the test fixtures up to date with the latest file modification times
|
||||
// thus ModTime is not under test
|
||||
fi := test.expected.FileInfo.(stereoscopeFile.ManualInfo)
|
||||
fi.ModTimeValue = actual[l.Coordinates].ModTime()
|
||||
test.expected.FileInfo = fi
|
||||
}
|
||||
|
||||
assert.True(t, test.expected.Equal(actual[l.Coordinates]))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package model
|
||||
|
||||
// Document represents the syft cataloging findings as a JSON document
|
||||
type Document struct {
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||
Distro LinuxRelease `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||
Packages []Package `json:"packages"` // Packages is the list of packages discovered
|
||||
Relationships []Relationship `json:"relationships"` // Relationships is a list of package-to-package, package-to-file, and file-to-package relationships
|
||||
Files []File `json:"files,omitempty"` // Files is a list of all files at their real path and any associated metadata about that file
|
||||
Artifacts []Package `json:"artifacts"` // Artifacts is the list of packages discovered and placed into the catalog
|
||||
ArtifactRelationships []Relationship `json:"artifactRelationships"`
|
||||
Files []File `json:"files,omitempty"` // note: must have omitempty
|
||||
Source Source `json:"source"` // Source represents the original object that was cataloged
|
||||
Distro LinuxRelease `json:"distro"` // Distro represents the Linux distribution that was detected from the source
|
||||
Descriptor Descriptor `json:"descriptor"` // Descriptor is a block containing self-describing information about syft
|
||||
Schema Schema `json:"schema"` // Schema is a block reserved for defining the version for the shape of this JSON document and where to find the schema document to validate the shape
|
||||
}
|
||||
|
||||
// Descriptor describes what created the document as well as surrounding metadata
|
||||
|
||||
@@ -35,12 +35,12 @@ func metadataType(metadata interface{}, legacy bool) string {
|
||||
// ToFormatModel transforms the sbom import a format-specific model.
|
||||
func ToFormatModel(s sbom.SBOM, cfg EncoderConfig) model.Document {
|
||||
return model.Document{
|
||||
Packages: toPackageModels(s.Artifacts.Packages, cfg),
|
||||
Relationships: toRelationshipModel(s.Relationships),
|
||||
Files: toFile(s),
|
||||
Source: toSourceModel(s.Source),
|
||||
Distro: toLinuxReleaser(s.Artifacts.LinuxDistribution),
|
||||
Descriptor: toDescriptor(s.Descriptor),
|
||||
Artifacts: toPackageModels(s.Artifacts.Packages, cfg),
|
||||
ArtifactRelationships: toRelationshipModel(s.Relationships),
|
||||
Files: toFile(s),
|
||||
Source: toSourceModel(s.Source),
|
||||
Distro: toLinuxReleaser(s.Artifacts.LinuxDistribution),
|
||||
Descriptor: toDescriptor(s.Descriptor),
|
||||
Schema: model.Schema{
|
||||
Version: internal.JSONSchemaVersion,
|
||||
URL: fmt.Sprintf("https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-%s.json", internal.JSONSchemaVersion),
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
func toSyftModel(doc model.Document) *sbom.SBOM {
|
||||
idAliases := make(map[string]string)
|
||||
|
||||
catalog := toSyftCatalog(doc.Packages, idAliases)
|
||||
catalog := toSyftCatalog(doc.Artifacts, idAliases)
|
||||
|
||||
fileArtifacts := toSyftFiles(doc.Files)
|
||||
|
||||
@@ -39,7 +39,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
Descriptor: toSyftDescriptor(doc.Descriptor),
|
||||
Relationships: warnConversionErrors(toSyftRelationships(&doc, catalog, doc.Relationships, idAliases)),
|
||||
Relationships: warnConversionErrors(toSyftRelationships(&doc, catalog, doc.ArtifactRelationships, idAliases)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ func Test_idsHaveChanged(t *testing.T) {
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{Path: "some/path"},
|
||||
},
|
||||
Packages: []model.Package{
|
||||
Artifacts: []model.Package{
|
||||
{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: "1",
|
||||
@@ -191,7 +191,7 @@ func Test_idsHaveChanged(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Relationships: []model.Relationship{
|
||||
ArtifactRelationships: []model.Relationship{
|
||||
{
|
||||
Parent: "1",
|
||||
Child: "2",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// NewDBCataloger returns a new cataloger object initialized for Alpine package DB flat-file stores.
|
||||
func NewDBCataloger() *generic.Cataloger {
|
||||
func NewDBCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("apk-db-cataloger").
|
||||
WithParserByGlobs(parseApkDB, pkg.ApkDBGlob)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// NewDBCataloger returns a new cataloger object initialized for arch linux pacman database flat-file stores.
|
||||
func NewDBCataloger() *generic.Cataloger {
|
||||
func NewDBCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("alpm-db-cataloger").
|
||||
WithParserByGlobs(parseAlpmDB, pkg.AlpmDBGlob)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
const catalogerName = "binary-cataloger"
|
||||
|
||||
func NewCataloger() *Cataloger {
|
||||
func NewCataloger() pkg.Cataloger {
|
||||
return &Cataloger{}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,42 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("postgresql-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-mysql-8.0.34",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/mysql-8.0.34",
|
||||
expected: pkg.Package{
|
||||
Name: "mysql",
|
||||
Version: "8.0.34",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/mysql@8.0.34",
|
||||
Locations: locations("mysql"),
|
||||
Metadata: metadata("mysql-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-mysql-5.6.51",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/mysql-5.6.51",
|
||||
expected: pkg.Package{
|
||||
Name: "mysql",
|
||||
Version: "5.6.51",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/mysql@5.6.51",
|
||||
Locations: locations("mysql"),
|
||||
Metadata: metadata("mysql-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-mariadb-10.6.15",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/mariadb-10.6.15",
|
||||
expected: pkg.Package{
|
||||
Name: "mariadb",
|
||||
Version: "10.6.15",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/mariadb@10.6.15",
|
||||
Locations: locations("mariadb"),
|
||||
Metadata: metadata("mariadb-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-traefik-2.9.6",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/traefik-2.9.6",
|
||||
@@ -312,6 +348,42 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-redis-7.0.14",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.0.14",
|
||||
expected: pkg.Package{
|
||||
Name: "redis",
|
||||
Version: "7.0.14",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/redis@7.0.14",
|
||||
Locations: locations("redis-server"),
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-redis-7.2.3-amd64",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-amd64",
|
||||
expected: pkg.Package{
|
||||
Name: "redis",
|
||||
Version: "7.2.3",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/redis@7.2.3",
|
||||
Locations: locations("redis-server"),
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-redis-7.2.3-arm64",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/redis-server-7.2.3-arm64",
|
||||
expected: pkg.Package{
|
||||
Name: "redis",
|
||||
Version: "7.2.3",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/redis@7.2.3",
|
||||
Locations: locations("redis-server"),
|
||||
Metadata: metadata("redis-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-libpython3.7.so",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/python-binary-lib-3.7",
|
||||
|
||||
@@ -58,8 +58,10 @@ var defaultClassifiers = []classifier{
|
||||
{
|
||||
Class: "redis-binary",
|
||||
FileGlob: "**/redis-server",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?s)payload %5.*(?P<version>\d.\d\.\d\d*?)[a-z0-9]{12}-[0-9]{19}`),
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
fileContentsVersionMatcher(`(?s)payload %5.*?(?P<version>\d.\d\.\d\d*)[a-z0-9]{12,15}-[0-9]{19}`),
|
||||
fileContentsVersionMatcher(`(?s)\x00(?P<version>\d.\d\.\d\d*)[a-z0-9]{12}-[0-9]{19}\x00.*?payload %5`),
|
||||
),
|
||||
Package: "redis",
|
||||
PURL: mustPURL("pkg:generic/redis@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:redislabs:redis:*:*:*:*:*:*:*:*"),
|
||||
@@ -67,10 +69,18 @@ var defaultClassifiers = []classifier{
|
||||
{
|
||||
Class: "java-binary-openjdk",
|
||||
FileGlob: "**/java",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
|
||||
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
|
||||
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
|
||||
EvidenceMatcher: evidenceMatchers(
|
||||
fileContentsVersionMatcher(
|
||||
// covers linux/amd64
|
||||
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
|
||||
// [NUL]openjdk[NUL]java[NUL]1.8[NUL]1.8.0_352-b08[NUL]
|
||||
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`,
|
||||
),
|
||||
fileContentsVersionMatcher(
|
||||
// covers linux/arm64
|
||||
`(?m)(?P<release>[0-9]+[.0-9]*)(\x00)+(?P<version>[0-9]+[^\x00]+)(\x00)+openjdk(\x00)+java\x00`,
|
||||
),
|
||||
),
|
||||
Package: "java",
|
||||
PURL: mustPURL("pkg:generic/java@version"),
|
||||
// TODO the updates might need to be part of the CPE, like: 1.8.0:update152
|
||||
@@ -209,6 +219,26 @@ var defaultClassifiers = []classifier{
|
||||
Package: "postgresql",
|
||||
PURL: mustPURL("pkg:generic/postgresql@version"),
|
||||
},
|
||||
{
|
||||
Class: "mysql-binary",
|
||||
FileGlob: "**/mysql",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
// ../../mysql-8.0.34
|
||||
// /mysql-5.6.51/bld/client
|
||||
`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
|
||||
Package: "mysql",
|
||||
PURL: mustPURL("pkg:generic/mysql@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "mariadb-binary",
|
||||
FileGlob: "**/mariadb",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
// 10.6.15-MariaDB
|
||||
`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
|
||||
Package: "mariadb",
|
||||
PURL: mustPURL("pkg:generic/mariadb@version"),
|
||||
},
|
||||
{
|
||||
Class: "rust-standard-library-linux",
|
||||
FileGlob: "**/libstd-????????????????.so",
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
The binary snippet was gathered with:
|
||||
|
||||
```bash
|
||||
$ cat ./original-mariadb | strings | grep '-MariaDB'
|
||||
# assert you can see the value
|
||||
|
||||
|
||||
$ xxd ./original-mariadb | grep '\-MariaDB'
|
||||
# get the address...
|
||||
|
||||
|
||||
$ xxd -s 0x003dd5c0 -l 40 ./original-mariadb
|
||||
|
||||
003dd5c0: 2900 4c69 6e75 7800 3130 2e36 2e31 352d ).Linux.10.6.15-
|
||||
003dd5d0: 4d61 7269 6144 4200 7265 6164 6c69 6e65 MariaDB.readline
|
||||
003dd5e0: 0078 3836 5f36 3400 .x86_64.
|
||||
|
||||
|
||||
$ dd if=./original-mariadb of=mariadb bs=1 skip=$((0x003dd5c0)) count=40
|
||||
|
||||
40+0 records in
|
||||
40+0 records out
|
||||
40 bytes transferred in 0.000264 secs (151515 bytes/sec)
|
||||
|
||||
|
||||
$ xxd mariadb
|
||||
|
||||
00000000: 2900 4c69 6e75 7800 3130 2e36 2e31 352d ).Linux.10.6.15-
|
||||
00000010: 4d61 7269 6144 4200 7265 6164 6c69 6e65 MariaDB.readline
|
||||
00000020: 0078 3836 5f36 3400 .x86_64.
|
||||
```
|
||||
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
The binary snippet was gathered with:
|
||||
|
||||
```bash
|
||||
$ cat ./original-mysql | strings | grep '5.6.51'
|
||||
# assert you can see the value
|
||||
|
||||
|
||||
$ xxd ./original-mysql | grep '5.6.51'
|
||||
# get the address...
|
||||
|
||||
|
||||
$ xxd -s 0x008f13d0 -l 100 original-mysql
|
||||
|
||||
008f13d0: 2d62 6163 6b75 702d 7265 7374 6f72 6572 -backup-restorer
|
||||
008f13e0: 2d6d 7973 716c 2d35 2e36 2f6d 7973 716c -mysql-5.6/mysql
|
||||
008f13f0: 2d35 2e36 2e35 312f 636c 6965 6e74 2f63 -5.6.51/client/c
|
||||
008f1400: 6f6d 706c 6574 696f 6e5f 6861 7368 2e63 ompletion_hash.c
|
||||
008f1410: 6300 2f76 6172 2f76 6361 702f 6461 7461 c./var/vcap/data
|
||||
008f1420: 2f63 6f6d 7069 6c65 2f64 6174 6162 6173 /compile/databas
|
||||
008f1430: 652d 6261 e-ba
|
||||
|
||||
|
||||
$ dd if=./original-mysql of=mysql bs=1 skip=$((0x008f13d0)) count=100
|
||||
|
||||
100+0 records in
|
||||
100+0 records out
|
||||
100 bytes transferred in 0.000642 secs (155763 bytes/sec)
|
||||
|
||||
|
||||
$ xxd mysql
|
||||
|
||||
00000000: 2d62 6163 6b75 702d 7265 7374 6f72 6572 -backup-restorer
|
||||
00000010: 2d6d 7973 716c 2d35 2e36 2f6d 7973 716c -mysql-5.6/mysql
|
||||
00000020: 2d35 2e36 2e35 312f 636c 6965 6e74 2f63 -5.6.51/client/c
|
||||
00000030: 6f6d 706c 6574 696f 6e5f 6861 7368 2e63 ompletion_hash.c
|
||||
00000040: 6300 2f76 6172 2f76 6361 702f 6461 7461 c./var/vcap/data
|
||||
00000050: 2f63 6f6d 7069 6c65 2f64 6174 6162 6173 /compile/databas
|
||||
00000060: 652d 6261 e-ba
|
||||
```
|
||||
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
The binary snippet was gathered with:
|
||||
|
||||
```bash
|
||||
$ cat ./original-mysql | strings | grep '8.0.34'
|
||||
# assert you can see the value
|
||||
|
||||
|
||||
$ xxd ./original-mysql | grep '8.0.34'
|
||||
# get the address...
|
||||
|
||||
|
||||
$ xxd -s 0x0014cd20 -l 100 original-mysql
|
||||
|
||||
0014cd20: 2069 7320 616c 7265 6164 7920 6c6f 6164 is already load
|
||||
0014cd30: 6564 0000 0000 0000 2e2e 2f2e 2e2f 6d79 ed......../../my
|
||||
0014cd40: 7371 6c2d 382e 302e 3334 2f73 716c 2d63 sql-8.0.34/sql-c
|
||||
0014cd50: 6f6d 6d6f 6e2f 636c 6965 6e74 5f70 6c75 ommon/client_plu
|
||||
0014cd60: 6769 6e2e 6363 002f 7573 722f 6c6f 6361 gin.cc./usr/loca
|
||||
0014cd70: 6c2f 6d79 7371 6c2f 6c69 622f 706c 7567 l/mysql/lib/plug
|
||||
0014cd80: 696e 0049 in.I
|
||||
|
||||
|
||||
|
||||
$ dd if=./original-mysql of=mysql bs=1 skip=$((0x0014cd20)) count=100
|
||||
|
||||
100+0 records in
|
||||
100+0 records out
|
||||
100 bytes transferred in 0.000519 secs (192678 bytes/sec)
|
||||
|
||||
|
||||
$ xxd mysql
|
||||
|
||||
00000000: 2069 7320 616c 7265 6164 7920 6c6f 6164 is already load
|
||||
00000010: 6564 0000 0000 0000 2e2e 2f2e 2e2f 6d79 ed......../../my
|
||||
00000020: 7371 6c2d 382e 302e 3334 2f73 716c 2d63 sql-8.0.34/sql-c
|
||||
00000030: 6f6d 6d6f 6e2f 636c 6965 6e74 5f70 6c75 ommon/client_plu
|
||||
00000040: 6769 6e2e 6363 002f 7573 722f 6c6f 6361 gin.cc./usr/loca
|
||||
00000050: 6c2f 6d79 7371 6c2f 6c69 622f 706c 7567 l/mysql/lib/plug
|
||||
00000060: 696e 0049 in.I
|
||||
```
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
package cataloger
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
@@ -10,10 +11,10 @@ import (
|
||||
// TODO: these field naming vs helper function naming schemes are inconsistent.
|
||||
type Config struct {
|
||||
Search SearchConfig
|
||||
Golang golang.GoCatalogerOpts
|
||||
LinuxKernel kernel.LinuxCatalogerConfig
|
||||
Golang golang.CatalogerConfig
|
||||
LinuxKernel kernel.LinuxKernelCatalogerConfig
|
||||
Python python.CatalogerConfig
|
||||
Java java.CatalogerOpts
|
||||
Java java.ArchiveCatalogerConfig
|
||||
Catalogers []string
|
||||
Parallelism int
|
||||
ExcludeBinaryOverlapByOwnership bool
|
||||
@@ -25,7 +26,7 @@ func DefaultConfig() Config {
|
||||
Parallelism: 1,
|
||||
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
|
||||
Python: python.DefaultCatalogerConfig(),
|
||||
Java: java.DefaultCatalogerOpts(),
|
||||
Java: java.DefaultArchiveCatalogerConfig(),
|
||||
ExcludeBinaryOverlapByOwnership: true,
|
||||
}
|
||||
}
|
||||
@@ -33,12 +34,14 @@ func DefaultConfig() Config {
|
||||
// JavaConfig merges relevant config values from Config to return a java.Config struct.
|
||||
// Values like IncludeUnindexedArchives and IncludeIndexedArchives are used across catalogers
|
||||
// and are not specific to Java requiring this merge.
|
||||
func (c Config) JavaConfig() java.Config {
|
||||
return java.Config{
|
||||
SearchUnindexedArchives: c.Search.IncludeUnindexedArchives,
|
||||
SearchIndexedArchives: c.Search.IncludeIndexedArchives,
|
||||
func (c Config) JavaConfig() java.ArchiveCatalogerConfig {
|
||||
return java.ArchiveCatalogerConfig{
|
||||
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
|
||||
IncludeUnindexedArchives: c.Search.IncludeUnindexedArchives,
|
||||
IncludeIndexedArchives: c.Search.IncludeIndexedArchives,
|
||||
},
|
||||
UseNetwork: c.Java.UseNetwork,
|
||||
MavenBaseURL: c.Java.MavenURL,
|
||||
MavenBaseURL: c.Java.MavenBaseURL,
|
||||
MaxParentRecursiveDepth: c.Java.MaxParentRecursiveDepth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,19 @@ Package cpp provides a concrete Cataloger implementations for the C/C++ language
|
||||
package cpp
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewConanCataloger returns a new C/C++ conanfile.txt and conan.lock cataloger object.
|
||||
func NewConanCataloger() *generic.Cataloger {
|
||||
func NewConanCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("conan-cataloger").
|
||||
WithParserByGlobs(parseConanfile, "**/conanfile.txt").
|
||||
WithParserByGlobs(parseConanlock, "**/conan.lock")
|
||||
}
|
||||
|
||||
// NewConanInfoCataloger returns a new C/C++ conaninfo.txt cataloger object.
|
||||
func NewConanInfoCataloger() *generic.Cataloger {
|
||||
func NewConanInfoCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("conan-info-cataloger").
|
||||
WithParserByGlobs(parseConaninfo, "**/conaninfo.txt")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ Package dart provides a concrete Cataloger implementations for the Dart language
|
||||
package dart
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewPubspecLockCataloger returns a new Dartlang cataloger object base on pubspec lock files.
|
||||
func NewPubspecLockCataloger() *generic.Cataloger {
|
||||
func NewPubspecLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dart-pubspec-lock-cataloger").
|
||||
WithParserByGlobs(parsePubspecLock, "**/pubspec.lock")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ Package debian provides a concrete Cataloger implementation relating to packages
|
||||
package debian
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewDBCataloger returns a new Deb package cataloger capable of parsing DPKG status DB flat-file stores.
|
||||
func NewDBCataloger() *generic.Cataloger {
|
||||
func NewDBCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dpkg-db-cataloger").
|
||||
// note: these globs have been intentionally split up in order to improve search performance,
|
||||
// please do NOT combine into: "**/var/lib/dpkg/{status,status.d/*}"
|
||||
|
||||
@@ -4,17 +4,18 @@ Package dotnet provides a concrete Cataloger implementation relating to packages
|
||||
package dotnet
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewDotnetDepsCataloger returns a new Dotnet cataloger object base on deps json files.
|
||||
func NewDotnetDepsCataloger() *generic.Cataloger {
|
||||
func NewDotnetDepsCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dotnet-deps-cataloger").
|
||||
WithParserByGlobs(parseDotnetDeps, "**/*.deps.json")
|
||||
}
|
||||
|
||||
// NewDotnetPortableExecutableCataloger returns a new Dotnet cataloger object base on portable executable files.
|
||||
func NewDotnetPortableExecutableCataloger() *generic.Cataloger {
|
||||
func NewDotnetPortableExecutableCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dotnet-portable-executable-cataloger").
|
||||
WithParserByGlobs(parseDotnetPortableExecutable, "**/*.dll", "**/*.exe")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package dotnet
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestCataloger_Globs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
cataloger *generic.Cataloger
|
||||
cataloger pkg.Cataloger
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -26,27 +26,29 @@ func parseDotnetPortableExecutable(_ file.Resolver, _ *generic.Environment, f fi
|
||||
|
||||
peFile, err := pe.NewBytes(by, &pe.Options{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create PE file instance: %w", err)
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
err = peFile.Parse()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse PE file: %w", err)
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
versionResources, err := peFile.ParseVersionResources()
|
||||
if err != nil {
|
||||
// this is not a fatal error, just log and continue
|
||||
// TODO: consider this case for "known unknowns" (same goes for cases below)
|
||||
log.Tracef("unable to parse version resources in PE file: %s", f.RealPath)
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
dotNetPkg, err := buildDotNetPackage(versionResources, f)
|
||||
if err != nil {
|
||||
// this is not a fatal error, just log and continue
|
||||
// TODO: consider this case for "known unknowns" (same goes for cases below)
|
||||
log.Tracef("unable to build dotnet package: %w", err)
|
||||
// TODO: known-unknown
|
||||
log.Tracef("unable to build dotnet package: %v", err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ Package elixir provides a concrete Cataloger implementation relating to packages
|
||||
package elixir
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewMixLockCataloger returns a cataloger object for Elixir mix.lock files.
|
||||
func NewMixLockCataloger() *generic.Cataloger {
|
||||
func NewMixLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("elixir-mix-lock-cataloger").
|
||||
WithParserByGlobs(parseMixLock, "**/mix.lock")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ Package erlang provides a concrete Cataloger implementation relating to packages
|
||||
package erlang
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewRebarLockCataloger returns a new cataloger instance for Erlang rebar.lock files.
|
||||
func NewRebarLockCataloger() *generic.Cataloger {
|
||||
func NewRebarLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("erlang-rebar-lock-cataloger").
|
||||
WithParserByGlobs(parseRebarLock, "**/rebar.lock")
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ Package gentoo provides a concrete Cataloger implementation related to packages
|
||||
package gentoo
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewPortageCataloger returns a new cataloger object initialized for Gentoo Portage package manager files (a flat-file store).
|
||||
func NewPortageCataloger() *generic.Cataloger {
|
||||
func NewPortageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("portage-cataloger").
|
||||
WithParserByGlobs(parsePortageContents, "**/var/db/pkg/*/*/CONTENTS")
|
||||
}
|
||||
|
||||
@@ -3,17 +3,20 @@ Package githubactions provides a concrete Cataloger implementation for GitHub Ac
|
||||
*/
|
||||
package githubactions
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewActionUsageCataloger returns GitHub Actions used within workflows and composite actions.
|
||||
func NewActionUsageCataloger() *generic.Cataloger {
|
||||
func NewActionUsageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("github-actions-usage-cataloger").
|
||||
WithParserByGlobs(parseWorkflowForActionUsage, "**/.github/workflows/*.yaml", "**/.github/workflows/*.yml").
|
||||
WithParserByGlobs(parseCompositeActionForActionUsage, "**/.github/actions/*/action.yml", "**/.github/actions/*/action.yaml")
|
||||
}
|
||||
|
||||
// NewWorkflowUsageCataloger returns shared workflows used within workflows.
|
||||
func NewWorkflowUsageCataloger() *generic.Cataloger {
|
||||
func NewWorkflowUsageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("github-action-workflow-usage-cataloger").
|
||||
WithParserByGlobs(parseWorkflowForWorkflowUsage, "**/.github/workflows/*.yaml", "**/.github/workflows/*.yml")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package githubactions
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestCataloger_Globs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
cataloger *generic.Cataloger
|
||||
cataloger pkg.Cataloger
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/event/monitor"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
@@ -20,31 +19,28 @@ import (
|
||||
var versionCandidateGroups = regexp.MustCompile(`(?P<version>\d+(\.\d+)?(\.\d+)?)(?P<candidate>\w*)`)
|
||||
|
||||
// NewGoModuleFileCataloger returns a new cataloger object that searches within go.mod files.
|
||||
func NewGoModuleFileCataloger(opts GoCatalogerOpts) pkg.Cataloger {
|
||||
func NewGoModuleFileCataloger(opts CatalogerConfig) pkg.Cataloger {
|
||||
c := goModCataloger{
|
||||
licenses: newGoLicenses(opts),
|
||||
}
|
||||
return &progressingCataloger{
|
||||
progress: c.licenses.progress,
|
||||
cataloger: generic.NewCataloger("go-module-file-cataloger").
|
||||
WithParserByGlobs(c.parseGoModFile, "**/go.mod"),
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoModuleBinaryCataloger returns a new cataloger object that searches within binaries built by the go compiler.
|
||||
func NewGoModuleBinaryCataloger(opts GoCatalogerOpts) pkg.Cataloger {
|
||||
func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
|
||||
c := goBinaryCataloger{
|
||||
licenses: newGoLicenses(opts),
|
||||
}
|
||||
return &progressingCataloger{
|
||||
progress: c.licenses.progress,
|
||||
cataloger: generic.NewCataloger("go-module-binary-cataloger").
|
||||
WithParserByMimeTypes(c.parseGoBinary, internal.ExecutableMIMETypeSet.List()...),
|
||||
}
|
||||
}
|
||||
|
||||
type progressingCataloger struct {
|
||||
progress *monitor.CatalogerTask
|
||||
cataloger *generic.Cataloger
|
||||
}
|
||||
|
||||
@@ -53,7 +49,6 @@ func (p *progressingCataloger) Name() string {
|
||||
}
|
||||
|
||||
func (p *progressingCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
defer p.progress.SetCompleted()
|
||||
pkgs, relationships, err := p.cataloger.Catalog(resolver)
|
||||
goCompilerPkgs := []pkg.Package{}
|
||||
totalLocations := file.NewLocationSet()
|
||||
@@ -76,6 +71,7 @@ func (p *progressingCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, [
|
||||
pkgs = append(pkgs, goCompilerPkgs...)
|
||||
return pkgs, relationships, err
|
||||
}
|
||||
|
||||
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
|
||||
stdlibCpe, err := generateStdlibCpe(version)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,7 +30,7 @@ func Test_Mod_Cataloger_Globs(t *testing.T) {
|
||||
FromDirectory(t, test.fixture).
|
||||
ExpectsResolverContentQueries(test.expected).
|
||||
IgnoreUnfulfilledPathResponses("src/go.sum").
|
||||
TestCataloger(t, NewGoModuleFileCataloger(GoCatalogerOpts{}))
|
||||
TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ func Test_Binary_Cataloger_Globs(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
ExpectsResolverContentQueries(test.expected).
|
||||
TestCataloger(t, NewGoModuleBinaryCataloger(GoCatalogerOpts{}))
|
||||
TestCataloger(t, NewGoModuleBinaryCataloger(CatalogerConfig{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultProxies = "https://proxy.golang.org,direct"
|
||||
directProxyOnly = "direct"
|
||||
)
|
||||
|
||||
var (
|
||||
directProxiesOnly = []string{directProxyOnly}
|
||||
)
|
||||
|
||||
type CatalogerConfig struct {
|
||||
SearchLocalModCacheLicenses bool `yaml:"search-local-mod-cache-licenses" json:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `yaml:"local-mod-cache-dir" json:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
SearchRemoteLicenses bool `yaml:"search-remote-licenses" json:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxies []string `yaml:"proxies,omitempty" json:"proxies,omitempty" mapstructure:"proxies"`
|
||||
NoProxy []string `yaml:"no-proxy,omitempty" json:"no-proxy,omitempty" mapstructure:"no-proxy"`
|
||||
}
|
||||
|
||||
// DefaultCatalogerConfig create a CatalogerConfig with default options, which includes:
|
||||
// - setting the default remote proxy if none is provided
|
||||
// - setting the default no proxy if none is provided
|
||||
// - setting the default local module cache dir if none is provided
|
||||
func DefaultCatalogerConfig() CatalogerConfig {
|
||||
g := CatalogerConfig{}
|
||||
|
||||
// first process the proxy settings
|
||||
if len(g.Proxies) == 0 {
|
||||
goProxy := os.Getenv("GOPROXY")
|
||||
if goProxy == "" {
|
||||
goProxy = defaultProxies
|
||||
}
|
||||
g = g.WithProxy(goProxy)
|
||||
}
|
||||
|
||||
// next process the gonoproxy settings
|
||||
if len(g.NoProxy) == 0 {
|
||||
goPrivate := os.Getenv("GOPRIVATE")
|
||||
goNoProxy := os.Getenv("GONOPROXY")
|
||||
// we only use the env var if it was not set explicitly
|
||||
if goPrivate != "" {
|
||||
g.NoProxy = append(g.NoProxy, strings.Split(goPrivate, ",")...)
|
||||
}
|
||||
|
||||
// next process the goprivate settings; we always add those
|
||||
if goNoProxy != "" {
|
||||
g.NoProxy = append(g.NoProxy, strings.Split(goNoProxy, ",")...)
|
||||
}
|
||||
}
|
||||
|
||||
if g.LocalModCacheDir == "" {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Debug("unable to determine user home dir: %v", err)
|
||||
} else {
|
||||
goPath = path.Join(homeDir, "go")
|
||||
}
|
||||
}
|
||||
if goPath != "" {
|
||||
g.LocalModCacheDir = path.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
func (g CatalogerConfig) WithSearchLocalModCacheLicenses(input bool) CatalogerConfig {
|
||||
g.SearchLocalModCacheLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g CatalogerConfig) WithLocalModCacheDir(input string) CatalogerConfig {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.LocalModCacheDir = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g CatalogerConfig) WithSearchRemoteLicenses(input bool) CatalogerConfig {
|
||||
g.SearchRemoteLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g CatalogerConfig) WithProxy(input string) CatalogerConfig {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
if input == "off" {
|
||||
input = directProxyOnly
|
||||
}
|
||||
g.Proxies = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
|
||||
func (g CatalogerConfig) WithNoProxy(input string) CatalogerConfig {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.NoProxy = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
+14
-14
@@ -34,7 +34,7 @@ func Test_Options(t *testing.T) {
|
||||
name string
|
||||
env map[string]string
|
||||
opts opts
|
||||
expected GoCatalogerOpts
|
||||
expected CatalogerConfig
|
||||
}{
|
||||
{
|
||||
name: "set via env defaults",
|
||||
@@ -45,12 +45,12 @@ func Test_Options(t *testing.T) {
|
||||
"GONOPROXY": "no.proxy",
|
||||
},
|
||||
opts: opts{},
|
||||
expected: GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: false,
|
||||
localModCacheDir: "/go/pkg/mod",
|
||||
searchRemoteLicenses: false,
|
||||
proxies: []string{"https://my.proxy"},
|
||||
noProxy: []string{"my.private", "no.proxy"},
|
||||
expected: CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: false,
|
||||
LocalModCacheDir: "/go/pkg/mod",
|
||||
SearchRemoteLicenses: false,
|
||||
Proxies: []string{"https://my.proxy"},
|
||||
NoProxy: []string{"my.private", "no.proxy"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,12 +68,12 @@ func Test_Options(t *testing.T) {
|
||||
proxy: "https://alt.proxy,direct",
|
||||
noProxy: "alt.no.proxy",
|
||||
},
|
||||
expected: GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: "/go-cache",
|
||||
searchRemoteLicenses: true,
|
||||
proxies: []string{"https://alt.proxy", "direct"},
|
||||
noProxy: []string{"alt.no.proxy"},
|
||||
expected: CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: true,
|
||||
LocalModCacheDir: "/go-cache",
|
||||
SearchRemoteLicenses: true,
|
||||
Proxies: []string{"https://alt.proxy", "direct"},
|
||||
NoProxy: []string{"alt.no.proxy"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func Test_Options(t *testing.T) {
|
||||
for k, v := range test.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
got := NewGoCatalogerOpts().
|
||||
got := DefaultCatalogerConfig().
|
||||
WithSearchLocalModCacheLicenses(test.opts.local).
|
||||
WithLocalModCacheDir(test.opts.cacheDir).
|
||||
WithSearchRemoteLicenses(test.opts.remote).
|
||||
@@ -29,16 +29,16 @@ import (
|
||||
)
|
||||
|
||||
type goLicenses struct {
|
||||
opts GoCatalogerOpts
|
||||
opts CatalogerConfig
|
||||
localModCacheResolver file.WritableResolver
|
||||
progress *monitor.CatalogerTask
|
||||
lowerLicenseFileNames *strset.Set
|
||||
}
|
||||
|
||||
func newGoLicenses(opts GoCatalogerOpts) goLicenses {
|
||||
func newGoLicenses(opts CatalogerConfig) goLicenses {
|
||||
return goLicenses{
|
||||
opts: opts,
|
||||
localModCacheResolver: modCacheResolver(opts.localModCacheDir),
|
||||
localModCacheResolver: modCacheResolver(opts.LocalModCacheDir),
|
||||
progress: &monitor.CatalogerTask{
|
||||
SubStatus: true,
|
||||
RemoveOnCompletion: true,
|
||||
@@ -107,7 +107,7 @@ func (c *goLicenses) getLicenses(resolver file.Resolver, moduleName, moduleVersi
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchLocalModCacheLicenses {
|
||||
if !c.opts.SearchLocalModCacheLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -117,11 +117,11 @@ func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]p
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchRemoteLicenses {
|
||||
if !c.opts.SearchRemoteLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proxies := remotesForModule(c.opts.proxies, c.opts.noProxy, moduleName)
|
||||
proxies := remotesForModule(c.opts.Proxies, c.opts.NoProxy, moduleName)
|
||||
|
||||
fsys, err := getModule(c.progress, proxies, moduleName, moduleVersion)
|
||||
if err != nil {
|
||||
@@ -231,15 +231,18 @@ func getModule(progress *monitor.CatalogerTask, proxies []string, moduleName, mo
|
||||
func getModuleProxy(progress *monitor.CatalogerTask, proxy string, moduleName string, moduleVersion string) (out fs.FS, _ error) {
|
||||
u := fmt.Sprintf("%s/%s/@v/%s.zip", proxy, moduleName, moduleVersion)
|
||||
progress.SetValue(u)
|
||||
|
||||
// get the module zip
|
||||
resp, err := http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
u = fmt.Sprintf("%s/%s/@v/%s.zip", proxy, strings.ToLower(moduleName), moduleVersion)
|
||||
progress.SetValue(u)
|
||||
|
||||
// try lowercasing it; some packages have mixed casing that really messes up the proxy
|
||||
resp, err = http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
@@ -250,19 +253,23 @@ func getModuleProxy(progress *monitor.CatalogerTask, proxy string, moduleName st
|
||||
return nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// read the zip
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, err = zip.NewReader(bytes.NewReader(b), resp.ContentLength)
|
||||
versionPath := findVersionPath(out, ".")
|
||||
out = getSubFS(out, versionPath)
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
||||
func findVersionPath(f fs.FS, dir string) string {
|
||||
list, _ := fs.ReadDir(f, dir)
|
||||
|
||||
for _, entry := range list {
|
||||
name := entry.Name()
|
||||
if strings.Contains(name, "@") {
|
||||
@@ -273,6 +280,7 @@ func findVersionPath(f fs.FS, dir string) string {
|
||||
return path.Join(name, found)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -282,7 +290,9 @@ func getModuleRepository(progress *monitor.CatalogerTask, moduleName string, mod
|
||||
if len(parts) > 2 {
|
||||
repoName = fmt.Sprintf("%s/%s/%s", parts[0], parts[1], parts[2])
|
||||
}
|
||||
|
||||
progress.SetValue(fmt.Sprintf("git: %s", repoName))
|
||||
|
||||
f := memfs.New()
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{
|
||||
@@ -292,6 +302,7 @@ func getModuleRepository(progress *monitor.CatalogerTask, moduleName string, mod
|
||||
Depth: 1,
|
||||
Progress: buf,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w -- %s", err, buf.String())
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(
|
||||
GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
CatalogerConfig{
|
||||
SearchLocalModCacheLicenses: true,
|
||||
LocalModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
},
|
||||
)
|
||||
licenses, err := l.getLicenses(fileresolver.Empty{}, test.name, test.version)
|
||||
@@ -154,10 +154,10 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(GoCatalogerOpts{
|
||||
searchRemoteLicenses: true,
|
||||
proxies: []string{server.URL},
|
||||
localModCacheDir: modDir,
|
||||
l := newGoLicenses(CatalogerConfig{
|
||||
SearchRemoteLicenses: true,
|
||||
Proxies: []string{server.URL},
|
||||
LocalModCacheDir: modDir,
|
||||
})
|
||||
|
||||
licenses, err := l.getLicenses(fileresolver.Empty{}, test.name, test.version)
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultProxies = "https://proxy.golang.org,direct"
|
||||
directProxyOnly = "direct"
|
||||
)
|
||||
|
||||
var (
|
||||
directProxiesOnly = []string{directProxyOnly}
|
||||
)
|
||||
|
||||
type GoCatalogerOpts struct {
|
||||
searchLocalModCacheLicenses bool
|
||||
localModCacheDir string
|
||||
searchRemoteLicenses bool
|
||||
proxies []string
|
||||
noProxy []string
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithSearchLocalModCacheLicenses(input bool) GoCatalogerOpts {
|
||||
g.searchLocalModCacheLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithLocalModCacheDir(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.localModCacheDir = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithSearchRemoteLicenses(input bool) GoCatalogerOpts {
|
||||
g.searchRemoteLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithProxy(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
if input == "off" {
|
||||
input = directProxyOnly
|
||||
}
|
||||
g.proxies = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithNoProxy(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.noProxy = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
|
||||
// NewGoCatalogerOpts create a GoCatalogerOpts with default options, which includes:
|
||||
// - setting the default remote proxy if none is provided
|
||||
// - setting the default no proxy if none is provided
|
||||
// - setting the default local module cache dir if none is provided
|
||||
func NewGoCatalogerOpts() GoCatalogerOpts {
|
||||
g := GoCatalogerOpts{}
|
||||
|
||||
// first process the proxy settings
|
||||
if len(g.proxies) == 0 {
|
||||
goProxy := os.Getenv("GOPROXY")
|
||||
if goProxy == "" {
|
||||
goProxy = defaultProxies
|
||||
}
|
||||
g = g.WithProxy(goProxy)
|
||||
}
|
||||
|
||||
// next process the gonoproxy settings
|
||||
if len(g.noProxy) == 0 {
|
||||
goPrivate := os.Getenv("GOPRIVATE")
|
||||
goNoProxy := os.Getenv("GONOPROXY")
|
||||
// we only use the env var if it was not set explicitly
|
||||
if goPrivate != "" {
|
||||
g.noProxy = append(g.noProxy, strings.Split(goPrivate, ",")...)
|
||||
}
|
||||
|
||||
// next process the goprivate settings; we always add those
|
||||
if goNoProxy != "" {
|
||||
g.noProxy = append(g.noProxy, strings.Split(goNoProxy, ",")...)
|
||||
}
|
||||
}
|
||||
|
||||
if g.localModCacheDir == "" {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Debug("unable to determine user home dir: %v", err)
|
||||
} else {
|
||||
goPath = path.Join(homeDir, "go")
|
||||
}
|
||||
}
|
||||
if goPath != "" {
|
||||
g.localModCacheDir = path.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
@@ -142,7 +142,7 @@ func Test_GoSumHashes(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
TestCataloger(t, NewGoModuleFileCataloger(GoCatalogerOpts{}))
|
||||
TestCataloger(t, NewGoModuleFileCataloger(CatalogerConfig{}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ Package haskell provides a concrete Cataloger implementation relating to package
|
||||
package haskell
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
// This hints at splitting these into multiple catalogers, but for now we'll keep them together.
|
||||
|
||||
// NewHackageCataloger returns a new Haskell cataloger object.
|
||||
func NewHackageCataloger() *generic.Cataloger {
|
||||
func NewHackageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("haskell-cataloger").
|
||||
WithParserByGlobs(parseStackYaml, "**/stack.yaml").
|
||||
WithParserByGlobs(parseStackLock, "**/stack.yaml.lock").
|
||||
|
||||
@@ -53,14 +53,14 @@ type archiveParser struct {
|
||||
contentPath string
|
||||
fileInfo archiveFilename
|
||||
detectNested bool
|
||||
cfg Config
|
||||
cfg ArchiveCatalogerConfig
|
||||
}
|
||||
|
||||
type genericArchiveParserAdapter struct {
|
||||
cfg Config
|
||||
cfg ArchiveCatalogerConfig
|
||||
}
|
||||
|
||||
func newGenericArchiveParserAdapter(cfg Config) genericArchiveParserAdapter {
|
||||
func newGenericArchiveParserAdapter(cfg ArchiveCatalogerConfig) genericArchiveParserAdapter {
|
||||
return genericArchiveParserAdapter{cfg: cfg}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func uniquePkgKey(groupID string, p *pkg.Package) string {
|
||||
|
||||
// newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover
|
||||
// and parse nested archives or ignore them.
|
||||
func newJavaArchiveParser(reader file.LocationReadCloser, detectNested bool, cfg Config) (*archiveParser, func(), error) {
|
||||
func newJavaArchiveParser(reader file.LocationReadCloser, detectNested bool, cfg ArchiveCatalogerConfig) (*archiveParser, func(), error) {
|
||||
// fetch the last element of the virtual path
|
||||
virtualElements := strings.Split(reader.Path(), ":")
|
||||
currentFilepath := virtualElements[len(virtualElements)-1]
|
||||
@@ -338,7 +338,7 @@ func artifactIDMatchesFilename(artifactID, fileName string) bool {
|
||||
return strings.HasPrefix(artifactID, fileName) || strings.HasSuffix(fileName, artifactID)
|
||||
}
|
||||
|
||||
func findPomLicenses(pomProjectObject *parsedPomProject, cfg Config) {
|
||||
func findPomLicenses(pomProjectObject *parsedPomProject, cfg ArchiveCatalogerConfig) {
|
||||
// If we don't have any licenses until now, and if we have a parent Pom, then we'll check the parent pom in maven central for licenses.
|
||||
if pomProjectObject != nil && pomProjectObject.Parent != nil && len(pomProjectObject.Licenses) == 0 {
|
||||
parentLicenses, err := recursivelyFindLicensesFromParentPom(
|
||||
@@ -373,11 +373,11 @@ func formatMavenPomURL(groupID, artifactID, version, mavenBaseURL string) (reque
|
||||
return requestURL, err
|
||||
}
|
||||
|
||||
func recursivelyFindLicensesFromParentPom(groupID, artifactID, version string, cfg Config) ([]string, error) {
|
||||
func recursivelyFindLicensesFromParentPom(groupID, artifactID, version string, cfg ArchiveCatalogerConfig) ([]string, error) {
|
||||
var licenses []string
|
||||
// As there can be nested parent poms, we'll recursively check for licenses until we reach the max depth
|
||||
for i := 0; i < cfg.MaxParentRecursiveDepth; i++ {
|
||||
parentPom, err := getPomFromMavenCentral(groupID, artifactID, version, cfg.MavenBaseURL)
|
||||
parentPom, err := getPomFromMavenRepo(groupID, artifactID, version, cfg.MavenBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -395,7 +395,7 @@ func recursivelyFindLicensesFromParentPom(groupID, artifactID, version string, c
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
func getPomFromMavenCentral(groupID, artifactID, version, mavenBaseURL string) (*gopom.Project, error) {
|
||||
func getPomFromMavenRepo(groupID, artifactID, version, mavenBaseURL string) (*gopom.Project, error) {
|
||||
requestURL, err := formatMavenPomURL(groupID, artifactID, version, mavenBaseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -542,7 +542,7 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) (
|
||||
|
||||
// discoverPkgsFromZip finds Java archives within Java archives, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package.
|
||||
func discoverPkgsFromZip(location file.Location, archivePath, contentPath string, fileManifest intFile.ZipFileManifest, parentPkg *pkg.Package, cfg Config) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromZip(location file.Location, archivePath, contentPath string, fileManifest intFile.ZipFileManifest, parentPkg *pkg.Package, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// search and parse pom.properties files & fetch the contents
|
||||
openers, err := intFile.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(false, archiveFormatGlobs...)...)
|
||||
if err != nil {
|
||||
@@ -553,7 +553,7 @@ func discoverPkgsFromZip(location file.Location, archivePath, contentPath string
|
||||
}
|
||||
|
||||
// discoverPkgsFromOpeners finds Java archives within the given files and associates them with the given parent package.
|
||||
func discoverPkgsFromOpeners(location file.Location, openers map[string]intFile.Opener, parentPkg *pkg.Package, cfg Config) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromOpeners(location file.Location, openers map[string]intFile.Opener, parentPkg *pkg.Package, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
@@ -582,7 +582,7 @@ func discoverPkgsFromOpeners(location file.Location, openers map[string]intFile.
|
||||
}
|
||||
|
||||
// discoverPkgsFromOpener finds Java archives within the given file.
|
||||
func discoverPkgsFromOpener(location file.Location, pathWithinArchive string, archiveOpener intFile.Opener, cfg Config) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromOpener(location file.Location, pathWithinArchive string, archiveOpener intFile.Opener, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
archiveReadCloser, err := archiveOpener.Open()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
|
||||
@@ -669,7 +669,7 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
|
||||
|
||||
// newPackageFromMavenData processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
|
||||
func newPackageFromMavenData(pomProperties pkg.JavaPomProperties, parsedPomProject *parsedPomProject, parentPkg *pkg.Package, location file.Location, cfg Config) *pkg.Package {
|
||||
func newPackageFromMavenData(pomProperties pkg.JavaPomProperties, parsedPomProject *parsedPomProject, parentPkg *pkg.Package, location file.Location, cfg ArchiveCatalogerConfig) *pkg.Package {
|
||||
// keep the artifact name within the virtual path if this package does not match the parent package
|
||||
vPathSuffix := ""
|
||||
groupID := ""
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
name string
|
||||
fixture string
|
||||
detectNested bool
|
||||
config Config
|
||||
config ArchiveCatalogerConfig
|
||||
requestPath string
|
||||
requestHandlers []handlerPath
|
||||
expectedLicenses []pkg.License
|
||||
@@ -87,7 +87,7 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
name: "searchMavenForLicenses returns the expected licenses when search is set to true",
|
||||
fixture: "opensaml-core-3.4.6",
|
||||
detectNested: false,
|
||||
config: Config{
|
||||
config: ArchiveCatalogerConfig{
|
||||
UseNetwork: true,
|
||||
MavenBaseURL: url,
|
||||
MaxParentRecursiveDepth: 2,
|
||||
@@ -161,7 +161,7 @@ func TestFormatMavenURL(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
requestURL, err := formatMavenPomURL(tc.groupID, tc.artifactID, tc.version, MavenBaseURL)
|
||||
requestURL, err := formatMavenPomURL(tc.groupID, tc.artifactID, tc.version, mavenBaseURL)
|
||||
assert.NoError(t, err, "expected no err; got %w", err)
|
||||
assert.Equal(t, tc.expected, requestURL)
|
||||
})
|
||||
@@ -401,7 +401,7 @@ func TestParseJar(t *testing.T) {
|
||||
parser, cleanupFn, err := newJavaArchiveParser(file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
}, false, Config{UseNetwork: false})
|
||||
}, false, ArchiveCatalogerConfig{UseNetwork: false})
|
||||
defer cleanupFn()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -667,7 +667,7 @@ func TestParseNestedJar(t *testing.T) {
|
||||
|
||||
fixture, err := os.Open(test.fixture)
|
||||
require.NoError(t, err)
|
||||
gap := newGenericArchiveParserAdapter(Config{})
|
||||
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
|
||||
|
||||
actual, _, err := gap.parseJavaArchive(nil, nil, file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
@@ -1089,7 +1089,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
}
|
||||
test.expectedParent.Locations = locations
|
||||
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, file.NewLocation(virtualPath), Config{})
|
||||
actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, file.NewLocation(virtualPath), DefaultArchiveCatalogerConfig())
|
||||
if test.expectedPackage == nil {
|
||||
require.Nil(t, actualPackage)
|
||||
} else {
|
||||
@@ -1309,7 +1309,7 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gap := newGenericArchiveParserAdapter(Config{})
|
||||
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
|
||||
if tt.assignParent {
|
||||
assignParent(&tt.expectedPkgs[0], tt.expectedPkgs[1:]...)
|
||||
}
|
||||
|
||||
@@ -4,23 +4,24 @@ Package java provides a concrete Cataloger implementation for packages relating
|
||||
package java
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewArchiveCataloger returns a new Java archive cataloger object for detecting packages with archives (jar, war, ear, par, sar, jpi, hpi, and native-image formats)
|
||||
func NewArchiveCataloger(cfg Config) *generic.Cataloger {
|
||||
func NewArchiveCataloger(cfg ArchiveCatalogerConfig) *generic.Cataloger {
|
||||
gap := newGenericArchiveParserAdapter(cfg)
|
||||
|
||||
c := generic.NewCataloger("java-archive-cataloger").
|
||||
WithParserByGlobs(gap.parseJavaArchive, archiveFormatGlobs...)
|
||||
|
||||
if cfg.SearchIndexedArchives {
|
||||
if cfg.IncludeIndexedArchives {
|
||||
// java archives wrapped within zip files
|
||||
gzp := newGenericZipWrappedJavaArchiveParser(cfg)
|
||||
c.WithParserByGlobs(gzp.parseZipWrappedJavaArchive, genericZipGlobs...)
|
||||
}
|
||||
|
||||
if cfg.SearchUnindexedArchives {
|
||||
if cfg.IncludeUnindexedArchives {
|
||||
// java archives wrapped within tar files
|
||||
gtp := newGenericTarWrappedJavaArchiveParser(cfg)
|
||||
c.WithParserByGlobs(gtp.parseTarWrappedJavaArchive, genericTarGlobs...)
|
||||
@@ -30,14 +31,14 @@ func NewArchiveCataloger(cfg Config) *generic.Cataloger {
|
||||
|
||||
// NewPomCataloger returns a cataloger capable of parsing dependencies from a pom.xml file.
|
||||
// Pom files list dependencies that maybe not be locally installed yet.
|
||||
func NewPomCataloger() *generic.Cataloger {
|
||||
func NewPomCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("java-pom-cataloger").
|
||||
WithParserByGlobs(parserPomXML, "**/pom.xml")
|
||||
}
|
||||
|
||||
// NewGradleLockfileCataloger returns a cataloger capable of parsing dependencies from a gradle.lockfile file.
|
||||
// Note: Older versions of lockfiles aren't supported yet
|
||||
func NewGradleLockfileCataloger() *generic.Cataloger {
|
||||
func NewGradleLockfileCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("java-gradle-lockfile-cataloger").
|
||||
WithParserByGlobs(parseGradleLockfile, gradleLockfileGlob)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package java
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
)
|
||||
|
||||
@@ -54,10 +55,16 @@ func Test_ArchiveCataloger_Globs(t *testing.T) {
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
ExpectsResolverContentQueries(test.expected).
|
||||
TestCataloger(t, NewArchiveCataloger(Config{
|
||||
SearchUnindexedArchives: true,
|
||||
SearchIndexedArchives: true,
|
||||
}))
|
||||
TestCataloger(t,
|
||||
NewArchiveCataloger(
|
||||
ArchiveCatalogerConfig{
|
||||
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
|
||||
IncludeIndexedArchives: true,
|
||||
IncludeUnindexedArchives: true,
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
package java
|
||||
|
||||
type Config struct {
|
||||
SearchUnindexedArchives bool
|
||||
SearchIndexedArchives bool
|
||||
UseNetwork bool
|
||||
MavenBaseURL string
|
||||
MaxParentRecursiveDepth int
|
||||
import "github.com/anchore/syft/syft/cataloging"
|
||||
|
||||
const mavenBaseURL = "https://repo1.maven.org/maven2"
|
||||
|
||||
type ArchiveCatalogerConfig struct {
|
||||
cataloging.ArchiveSearchConfig `yaml:",inline" json:"" mapstructure:",squash"`
|
||||
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
|
||||
MavenBaseURL string `yaml:"maven-base-url" json:"maven-base-url" mapstructure:"maven-base-url"`
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
}
|
||||
|
||||
func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
|
||||
return ArchiveCatalogerConfig{
|
||||
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
|
||||
IncludeIndexedArchives: true,
|
||||
IncludeUnindexedArchives: false,
|
||||
},
|
||||
UseNetwork: false,
|
||||
MavenBaseURL: mavenBaseURL,
|
||||
MaxParentRecursiveDepth: 5,
|
||||
}
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithUseNetwork(input bool) ArchiveCatalogerConfig {
|
||||
j.UseNetwork = input
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithMavenBaseURL(input string) ArchiveCatalogerConfig {
|
||||
if input != "" {
|
||||
j.MavenBaseURL = input
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithArchiveTraversal(search cataloging.ArchiveSearchConfig, maxDepth int) ArchiveCatalogerConfig {
|
||||
if maxDepth > 0 {
|
||||
j.MaxParentRecursiveDepth = maxDepth
|
||||
}
|
||||
j.ArchiveSearchConfig = search
|
||||
return j
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ const nativeImageInvalidIndexError = "parsing the executable file generated an i
|
||||
const nativeImageMissingExportedDataDirectoryError = "exported data directory is missing"
|
||||
|
||||
// newNativeImageCataloger returns a new Native Image cataloger object.
|
||||
func NewNativeImageCataloger() *NativeImageCataloger {
|
||||
func NewNativeImageCataloger() pkg.Cataloger {
|
||||
return &NativeImageCataloger{}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package java
|
||||
|
||||
const MavenBaseURL = "https://repo1.maven.org/maven2"
|
||||
|
||||
type CatalogerOpts struct {
|
||||
UseNetwork bool
|
||||
MavenURL string
|
||||
MaxParentRecursiveDepth int
|
||||
}
|
||||
|
||||
func (j CatalogerOpts) WithUseNetwork(input bool) CatalogerOpts {
|
||||
j.UseNetwork = input
|
||||
return j
|
||||
}
|
||||
|
||||
func (j CatalogerOpts) WithMavenURL(input string) CatalogerOpts {
|
||||
if input != "" {
|
||||
j.MavenURL = input
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func (j CatalogerOpts) WithMaxParentRecursiveDepth(input int) CatalogerOpts {
|
||||
if input > 0 {
|
||||
j.MaxParentRecursiveDepth = input
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func DefaultCatalogerOpts() CatalogerOpts {
|
||||
return CatalogerOpts{
|
||||
UseNetwork: false,
|
||||
MavenURL: MavenBaseURL,
|
||||
MaxParentRecursiveDepth: 5,
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,10 @@ var genericTarGlobs = []string{
|
||||
// a file listing within the archive you must decompress the entire archive and seek through all of the entries.
|
||||
|
||||
type genericTarWrappedJavaArchiveParser struct {
|
||||
cfg Config
|
||||
cfg ArchiveCatalogerConfig
|
||||
}
|
||||
|
||||
func newGenericTarWrappedJavaArchiveParser(cfg Config) genericTarWrappedJavaArchiveParser {
|
||||
func newGenericTarWrappedJavaArchiveParser(cfg ArchiveCatalogerConfig) genericTarWrappedJavaArchiveParser {
|
||||
return genericTarWrappedJavaArchiveParser{
|
||||
cfg: cfg,
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (gtp genericTarWrappedJavaArchiveParser) parseTarWrappedJavaArchive(_ file.
|
||||
return discoverPkgsFromTar(reader.Location, archivePath, contentPath, gtp.cfg)
|
||||
}
|
||||
|
||||
func discoverPkgsFromTar(location file.Location, archivePath, contentPath string, cfg Config) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromTar(location file.Location, archivePath, contentPath string, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
openers, err := intFile.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err)
|
||||
|
||||
@@ -40,7 +40,7 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
gtp := newGenericTarWrappedJavaArchiveParser(Config{})
|
||||
gtp := newGenericTarWrappedJavaArchiveParser(ArchiveCatalogerConfig{})
|
||||
actualPkgs, _, err := gtp.parseTarWrappedJavaArchive(nil, nil, file.LocationReadCloser{
|
||||
Location: file.NewLocation(test.fixture),
|
||||
ReadCloser: fixture,
|
||||
|
||||
@@ -19,10 +19,10 @@ var genericZipGlobs = []string{
|
||||
// parseZipWrappedJavaArchive is a parser function for java archive contents contained within arbitrary zip files.
|
||||
|
||||
type genericZipWrappedJavaArchiveParser struct {
|
||||
cfg Config
|
||||
cfg ArchiveCatalogerConfig
|
||||
}
|
||||
|
||||
func newGenericZipWrappedJavaArchiveParser(cfg Config) genericZipWrappedJavaArchiveParser {
|
||||
func newGenericZipWrappedJavaArchiveParser(cfg ArchiveCatalogerConfig) genericZipWrappedJavaArchiveParser {
|
||||
return genericZipWrappedJavaArchiveParser{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) {
|
||||
t.Fatalf("failed to open fixture: %+v", err)
|
||||
}
|
||||
|
||||
gzp := newGenericZipWrappedJavaArchiveParser(Config{})
|
||||
gzp := newGenericZipWrappedJavaArchiveParser(ArchiveCatalogerConfig{})
|
||||
|
||||
actualPkgs, _, err := gzp.parseZipWrappedJavaArchive(nil, nil, file.LocationReadCloser{
|
||||
Location: file.NewLocation(test.fixture),
|
||||
|
||||
@@ -4,17 +4,18 @@ Package javascript provides a concrete Cataloger implementation for packages rel
|
||||
package javascript
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewPackageCataloger returns a new cataloger object for NPM.
|
||||
func NewPackageCataloger() *generic.Cataloger {
|
||||
func NewPackageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("javascript-package-cataloger").
|
||||
WithParserByGlobs(parsePackageJSON, "**/package.json")
|
||||
}
|
||||
|
||||
// NewLockCataloger returns a new cataloger object for NPM (and NPM-adjacent, such as yarn) lock files.
|
||||
func NewLockCataloger() *generic.Cataloger {
|
||||
func NewLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("javascript-lock-cataloger").
|
||||
WithParserByGlobs(parsePackageLock, "**/package-lock.json").
|
||||
WithParserByGlobs(parseYarnLock, "**/yarn.lock").
|
||||
|
||||
@@ -15,16 +15,16 @@ import (
|
||||
|
||||
var _ pkg.Cataloger = (*LinuxKernelCataloger)(nil)
|
||||
|
||||
type LinuxCatalogerConfig struct {
|
||||
CatalogModules bool
|
||||
type LinuxKernelCatalogerConfig struct {
|
||||
CatalogModules bool `yaml:"catalog-modules" json:"catalog-modules" mapstructure:"catalog-modules"`
|
||||
}
|
||||
|
||||
type LinuxKernelCataloger struct {
|
||||
cfg LinuxCatalogerConfig
|
||||
cfg LinuxKernelCatalogerConfig
|
||||
}
|
||||
|
||||
func DefaultLinuxCatalogerConfig() LinuxCatalogerConfig {
|
||||
return LinuxCatalogerConfig{
|
||||
func DefaultLinuxCatalogerConfig() LinuxKernelCatalogerConfig {
|
||||
return LinuxKernelCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ var kernelModuleGlobs = []string{
|
||||
}
|
||||
|
||||
// NewLinuxKernelCataloger returns a new kernel files cataloger object.
|
||||
func NewLinuxKernelCataloger(cfg LinuxCatalogerConfig) *LinuxKernelCataloger {
|
||||
func NewLinuxKernelCataloger(cfg LinuxKernelCatalogerConfig) *LinuxKernelCataloger {
|
||||
return &LinuxKernelCataloger{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func Test_KernelCataloger(t *testing.T) {
|
||||
Expects(expectedPkgs, expectedRelationships).
|
||||
TestCataloger(t,
|
||||
NewLinuxKernelCataloger(
|
||||
LinuxCatalogerConfig{
|
||||
LinuxKernelCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
},
|
||||
),
|
||||
|
||||
@@ -19,7 +19,7 @@ const catalogerName = "nix-store-cataloger"
|
||||
// StoreCataloger finds package outputs installed in the Nix store location (/nix/store/*).
|
||||
type StoreCataloger struct{}
|
||||
|
||||
func NewStoreCataloger() *StoreCataloger {
|
||||
func NewStoreCataloger() pkg.Cataloger {
|
||||
return &StoreCataloger{}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ Package php provides a concrete Cataloger implementation relating to packages wi
|
||||
package php
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
@@ -11,13 +12,13 @@ import (
|
||||
// semantic meanings. The lock file represents what should be installed, whereas the installed file represents what is installed.
|
||||
|
||||
// NewComposerInstalledCataloger returns a new cataloger for PHP installed.json files.
|
||||
func NewComposerInstalledCataloger() *generic.Cataloger {
|
||||
func NewComposerInstalledCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("php-composer-installed-cataloger").
|
||||
WithParserByGlobs(parseInstalledJSON, "**/installed.json")
|
||||
}
|
||||
|
||||
// NewComposerLockCataloger returns a new cataloger for PHP composer.lock files.
|
||||
func NewComposerLockCataloger() *generic.Cataloger {
|
||||
func NewComposerLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("php-composer-lock-cataloger").
|
||||
WithParserByGlobs(parseComposerLock, "**/composer.lock")
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ Package python provides a concrete Cataloger implementation relating to packages
|
||||
package python
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
const eggInfoGlob = "**/*.egg-info"
|
||||
|
||||
type CatalogerConfig struct {
|
||||
GuessUnpinnedRequirements bool
|
||||
GuessUnpinnedRequirements bool `yaml:"guess-unpinned-requirements" json:"guess-unpinned-requirements" mapstructure:"guess-unpinned-requirements"`
|
||||
}
|
||||
|
||||
func DefaultCatalogerConfig() CatalogerConfig {
|
||||
@@ -30,7 +31,7 @@ func NewPackageCataloger(cfg CatalogerConfig) *generic.Cataloger {
|
||||
}
|
||||
|
||||
// NewInstalledPackageCataloger returns a new cataloger for python packages within egg or wheel installation directories.
|
||||
func NewInstalledPackageCataloger() *generic.Cataloger {
|
||||
func NewInstalledPackageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("python-installed-package-cataloger").
|
||||
WithParserByGlobs(
|
||||
parseWheelOrEgg,
|
||||
|
||||
@@ -4,11 +4,12 @@ Package r provides a concrete Cataloger implementation relating to packages with
|
||||
package r
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewPackageCataloger returns a new R cataloger object based on detection of R package DESCRIPTION files.
|
||||
func NewPackageCataloger() *generic.Cataloger {
|
||||
func NewPackageCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("r-package-cataloger").
|
||||
WithParserByGlobs(parseDescriptionFile, "**/DESCRIPTION")
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// NewDBCataloger returns a new RPM DB cataloger object.
|
||||
func NewDBCataloger() *generic.Cataloger {
|
||||
func NewDBCataloger() pkg.Cataloger {
|
||||
// check if a sqlite driver is available
|
||||
if !isSqliteDriverAvailable() {
|
||||
log.Warnf("sqlite driver is not available, newer RPM databases might not be cataloged")
|
||||
@@ -24,7 +24,7 @@ func NewDBCataloger() *generic.Cataloger {
|
||||
}
|
||||
|
||||
// NewArchiveCataloger returns a new RPM file cataloger object.
|
||||
func NewArchiveCataloger() *generic.Cataloger {
|
||||
func NewArchiveCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("rpm-archive-cataloger").
|
||||
WithParserByGlobs(parseRpmArchive, "**/*.rpm")
|
||||
}
|
||||
|
||||
@@ -4,23 +4,24 @@ Package ruby provides a concrete Cataloger implementation relating to packages w
|
||||
package ruby
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewGemFileLockCataloger returns a new Bundler cataloger object tailored for parsing index-oriented files (e.g. Gemfile.lock).
|
||||
func NewGemFileLockCataloger() *generic.Cataloger {
|
||||
func NewGemFileLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("ruby-gemfile-cataloger").
|
||||
WithParserByGlobs(parseGemFileLockEntries, "**/Gemfile.lock")
|
||||
}
|
||||
|
||||
// NewInstalledGemSpecCataloger returns a new Bundler cataloger object tailored for detecting installations of gems (e.g. Gemspec).
|
||||
func NewInstalledGemSpecCataloger() *generic.Cataloger {
|
||||
func NewInstalledGemSpecCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("ruby-installed-gemspec-cataloger").
|
||||
WithParserByGlobs(parseGemSpecEntries, "**/specifications/**/*.gemspec")
|
||||
}
|
||||
|
||||
// NewGemSpecCataloger looks for gems without the additional requirement of the gem being installed.
|
||||
func NewGemSpecCataloger() *generic.Cataloger {
|
||||
func NewGemSpecCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("ruby-gemspec-cataloger").
|
||||
WithParserByGlobs(parseGemSpecEntries, "**/*.gemspec")
|
||||
}
|
||||
@@ -5,18 +5,19 @@ package rust
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
// NewCargoLockCataloger returns a new Rust Cargo lock file cataloger object.
|
||||
func NewCargoLockCataloger() *generic.Cataloger {
|
||||
func NewCargoLockCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("rust-cargo-lock-cataloger").
|
||||
WithParserByGlobs(parseCargoLock, "**/Cargo.lock")
|
||||
}
|
||||
|
||||
// NewAuditBinaryCataloger returns a new Rust auditable binary cataloger object that can detect dependencies
|
||||
// in binaries produced with https://github.com/Shnatsel/rust-audit
|
||||
func NewAuditBinaryCataloger() *generic.Cataloger {
|
||||
func NewAuditBinaryCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("cargo-auditable-binary-cataloger").
|
||||
WithParserByMimeTypes(parseAuditBinary, internal.ExecutableMIMETypeSet.List()...)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
const catalogerName = "sbom-cataloger"
|
||||
|
||||
// NewCataloger returns a new SBOM cataloger object loaded from saved SBOM JSON.
|
||||
func NewCataloger() *generic.Cataloger {
|
||||
func NewCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger(catalogerName).
|
||||
WithParserByGlobs(parseSBOM,
|
||||
"**/*.syft.json",
|
||||
|
||||
@@ -4,16 +4,17 @@ Package swift provides a concrete Cataloger implementation relating to packages
|
||||
package swift
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
func NewSwiftPackageManagerCataloger() *generic.Cataloger {
|
||||
func NewSwiftPackageManagerCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("swift-package-manager-cataloger").
|
||||
WithParserByGlobs(parsePackageResolved, "**/Package.resolved", "**/.package.resolved")
|
||||
}
|
||||
|
||||
// NewCocoapodsCataloger returns a new Swift Cocoapods lock file cataloger object.
|
||||
func NewCocoapodsCataloger() *generic.Cataloger {
|
||||
func NewCocoapodsCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("cocoapods-cataloger").
|
||||
WithParserByGlobs(parsePodfileLock, "**/Podfile.lock")
|
||||
}
|
||||
|
||||
@@ -37,11 +37,17 @@ func NewCollection(pkgs ...Package) *Collection {
|
||||
|
||||
// PackageCount returns the total number of packages that have been added.
|
||||
func (c *Collection) PackageCount() int {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return len(c.byID)
|
||||
}
|
||||
|
||||
// Package returns the package with the given ID.
|
||||
func (c *Collection) Package(id artifact.ID) *Package {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
v, exists := c.byID[id]
|
||||
if !exists {
|
||||
return nil
|
||||
@@ -57,16 +63,31 @@ func (c *Collection) Package(id artifact.ID) *Package {
|
||||
|
||||
// PackagesByPath returns all packages that were discovered from the given path.
|
||||
func (c *Collection) PackagesByPath(path string) []Package {
|
||||
return c.Packages(c.idsByPath[path].slice)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.packages(c.idsByPath[path].slice)
|
||||
}
|
||||
|
||||
// PackagesByName returns all packages that were discovered with a matching name.
|
||||
func (c *Collection) PackagesByName(name string) []Package {
|
||||
return c.Packages(c.idsByName[name].slice)
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.packages(c.idsByName[name].slice)
|
||||
}
|
||||
|
||||
// Packages returns all packages for the given ID.
|
||||
func (c *Collection) Packages(ids []artifact.ID) (result []Package) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
return c.packages(ids)
|
||||
}
|
||||
|
||||
func (c *Collection) packages(ids []artifact.ID) (result []Package) {
|
||||
// note: read lock must be held by caller
|
||||
|
||||
for _, i := range ids {
|
||||
p, exists := c.byID[i]
|
||||
if exists {
|
||||
@@ -105,6 +126,8 @@ func (c *Collection) Add(pkgs ...Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) addToIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
c.byID[p.id] = p
|
||||
c.addNameToIndex(p)
|
||||
c.addTypeToIndex(p)
|
||||
@@ -112,18 +135,24 @@ func (c *Collection) addToIndex(p Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) addNameToIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
nameIndex := c.idsByName[p.Name]
|
||||
nameIndex.add(p.id)
|
||||
c.idsByName[p.Name] = nameIndex
|
||||
}
|
||||
|
||||
func (c *Collection) addTypeToIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
typeIndex := c.idsByType[p.Type]
|
||||
typeIndex.add(p.id)
|
||||
c.idsByType[p.Type] = typeIndex
|
||||
}
|
||||
|
||||
func (c *Collection) addPathsToIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
observedPaths := strset.New()
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if l.RealPath != "" && !observedPaths.Has(l.RealPath) {
|
||||
@@ -138,6 +167,8 @@ func (c *Collection) addPathsToIndex(p Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) addPathToIndex(id artifact.ID, path string) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
pathIndex := c.idsByPath[path]
|
||||
pathIndex.add(id)
|
||||
c.idsByPath[path] = pathIndex
|
||||
@@ -161,18 +192,24 @@ func (c *Collection) Delete(ids ...artifact.ID) {
|
||||
}
|
||||
|
||||
func (c *Collection) deleteNameFromIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
nameIndex := c.idsByName[p.Name]
|
||||
nameIndex.delete(p.id)
|
||||
c.idsByName[p.Name] = nameIndex
|
||||
}
|
||||
|
||||
func (c *Collection) deleteTypeFromIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
typeIndex := c.idsByType[p.Type]
|
||||
typeIndex.delete(p.id)
|
||||
c.idsByType[p.Type] = typeIndex
|
||||
}
|
||||
|
||||
func (c *Collection) deletePathsFromIndex(p Package) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
observedPaths := strset.New()
|
||||
for _, l := range p.Locations.ToSlice() {
|
||||
if l.RealPath != "" && !observedPaths.Has(l.RealPath) {
|
||||
@@ -187,6 +224,8 @@ func (c *Collection) deletePathsFromIndex(p Package) {
|
||||
}
|
||||
|
||||
func (c *Collection) deletePathFromIndex(id artifact.ID, path string) {
|
||||
// note: write lock must be held by caller
|
||||
|
||||
pathIndex := c.idsByPath[path]
|
||||
pathIndex.delete(id)
|
||||
if len(pathIndex.slice) == 0 {
|
||||
@@ -201,10 +240,15 @@ func (c *Collection) Enumerate(types ...Type) <-chan Package {
|
||||
channel := make(chan Package)
|
||||
go func() {
|
||||
defer close(channel)
|
||||
|
||||
if c == nil {
|
||||
// we should allow enumerating from a catalog that was never created (which will result in no packages enumerated)
|
||||
return
|
||||
}
|
||||
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
for ty, ids := range c.idsByType {
|
||||
if len(types) != 0 {
|
||||
found := false
|
||||
@@ -159,11 +159,10 @@ func (s FileSource) FileResolver(_ Scope) (file.Resolver, error) {
|
||||
}
|
||||
isArchiveAnalysis := fi.IsDir()
|
||||
|
||||
absAnalysisPath, err := filepath.Abs(s.analysisPath)
|
||||
absParentDir, err := absoluteSymlinkFreePathToParent(s.analysisPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get absolute path for analysis path=%q: %w", s.analysisPath, err)
|
||||
return nil, err
|
||||
}
|
||||
absParentDir := filepath.Dir(absAnalysisPath)
|
||||
|
||||
var res *fileresolver.Directory
|
||||
if isArchiveAnalysis {
|
||||
@@ -210,6 +209,18 @@ func (s FileSource) FileResolver(_ Scope) (file.Resolver, error) {
|
||||
return s.resolver, nil
|
||||
}
|
||||
|
||||
func absoluteSymlinkFreePathToParent(path string) (string, error) {
|
||||
absAnalysisPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for analysis path=%q: %w", path, err)
|
||||
}
|
||||
dereferencedAbsAnalysisPath, err := filepath.EvalSymlinks(absAnalysisPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for analysis path=%q: %w", path, err)
|
||||
}
|
||||
return filepath.Dir(dereferencedAbsAnalysisPath), nil
|
||||
}
|
||||
|
||||
func (s *FileSource) Close() error {
|
||||
if s.closer == nil {
|
||||
return nil
|
||||
|
||||
@@ -48,6 +48,22 @@ func TestNewFromFile(t *testing.T) {
|
||||
},
|
||||
expRefs: 1,
|
||||
},
|
||||
{
|
||||
desc: "normal path",
|
||||
input: "test-fixtures/actual-path/empty",
|
||||
testPathFn: func(resolver file.Resolver) ([]file.Location, error) {
|
||||
return resolver.FilesByPath("empty")
|
||||
},
|
||||
expRefs: 1,
|
||||
},
|
||||
{
|
||||
desc: "path containing symlink",
|
||||
input: "test-fixtures/symlink/empty",
|
||||
testPathFn: func(resolver file.Resolver) ([]file.Location, error) {
|
||||
return resolver.FilesByPath("empty")
|
||||
},
|
||||
expRefs: 1,
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
|
||||
@@ -7,11 +7,11 @@ type Scope string
|
||||
|
||||
const (
|
||||
// UnknownScope is the default scope
|
||||
UnknownScope Scope = "UnknownScope"
|
||||
UnknownScope Scope = "unknown-scope"
|
||||
// SquashedScope indicates to only catalog content visible from the squashed filesystem representation (what can be seen only within the container at runtime)
|
||||
SquashedScope Scope = "Squashed"
|
||||
SquashedScope Scope = "squashed"
|
||||
// AllLayersScope indicates to catalog content on all layers, regardless if it is visible from the container at runtime.
|
||||
AllLayersScope Scope = "AllLayers"
|
||||
AllLayersScope Scope = "all-layers"
|
||||
)
|
||||
|
||||
// AllScopes is a slice containing all possible scope options
|
||||
@@ -23,9 +23,9 @@ var AllScopes = []Scope{
|
||||
// ParseScope returns a scope as indicated from the given string.
|
||||
func ParseScope(userStr string) Scope {
|
||||
switch strings.ToLower(userStr) {
|
||||
case strings.ToLower(SquashedScope.String()):
|
||||
case SquashedScope.String():
|
||||
return SquashedScope
|
||||
case "all-layers", strings.ToLower(AllLayersScope.String()):
|
||||
case "alllayers", AllLayersScope.String():
|
||||
return AllLayersScope
|
||||
}
|
||||
return UnknownScope
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseScope(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want Scope
|
||||
}{
|
||||
// go cases
|
||||
{
|
||||
name: "squashed",
|
||||
want: SquashedScope,
|
||||
},
|
||||
{
|
||||
name: "all-layers",
|
||||
want: AllLayersScope,
|
||||
},
|
||||
// fall back to unknown
|
||||
{
|
||||
name: "make-believe",
|
||||
want: UnknownScope,
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
want: UnknownScope,
|
||||
},
|
||||
{
|
||||
name: " ",
|
||||
want: UnknownScope,
|
||||
},
|
||||
// to support the original value
|
||||
|
||||
{
|
||||
name: "Squashed",
|
||||
want: SquashedScope,
|
||||
},
|
||||
{
|
||||
name: "AllLayers",
|
||||
want: AllLayersScope,
|
||||
},
|
||||
// case insensitive
|
||||
{
|
||||
name: "alLlaYerS",
|
||||
want: AllLayersScope,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, ParseScope(tt.name))
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
actual-path
|
||||
@@ -0,0 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_RequestedPathIncludesSymlink(t *testing.T) {
|
||||
// path contains a symlink
|
||||
path := "test-fixtures/image-pkg-coverage/pkgs/java/example-java-app-maven-0.1.0.jar"
|
||||
_, stdout, _ := runSyft(t, nil, "packages", path)
|
||||
assert.Contains(t, stdout, "example-java-app-maven")
|
||||
}
|
||||
@@ -78,10 +78,9 @@ func TestConvertCmd(t *testing.T) {
|
||||
}()
|
||||
|
||||
opts := &commands.ConvertOptions{
|
||||
Output: options.Output{
|
||||
Outputs: []string{fmt.Sprintf("%s=%s", test.format.ID().String(), formatFile.Name())},
|
||||
},
|
||||
Output: options.DefaultOutput(),
|
||||
}
|
||||
opts.Outputs = []string{fmt.Sprintf("%s=%s", test.format.ID().String(), formatFile.Name())}
|
||||
require.NoError(t, opts.PostLoad())
|
||||
|
||||
// stdout reduction of test noise
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestJavaPURLs(t *testing.T) {
|
||||
// syft anchore/test_images:java-56d52bc -o template -t /tmp/test.templ | grep 'pkg:maven' | sort | uniq >> test/integration/java_purl_test.go
|
||||
// where the template is:
|
||||
/*
|
||||
{{ range .Packages}}"{{.Name}}@{{.Version}}":"{{.PURL}}",
|
||||
{{ range .Artifacts}}"{{.Name}}@{{.Version}}":"{{.PURL}}",
|
||||
{{ end }}
|
||||
*/
|
||||
// The map was then hand-edited for correctness by comparing to Maven Central.
|
||||
@@ -159,12 +159,12 @@ var expectedPURLs = map[string]string{
|
||||
"sezpoz@1.7": "pkg:maven/net.java.sezpoz/sezpoz@1.7",
|
||||
"slave@": "pkg:maven/slave/slave",
|
||||
"slide-webdavlib@2.1": "pkg:maven/slide-webdavlib/slide-webdavlib@2.1",
|
||||
"spring-aop@2.5": "pkg:maven/org.springframework.bundle.spring.aop/spring-aop@2.5",
|
||||
"spring-aop@2.5": "pkg:maven/org.springframework/spring-aop@2.5",
|
||||
"spring-beans@2.5": "pkg:maven/org.springframework/spring-beans@2.5",
|
||||
"spring-context@2.5": "pkg:maven/org.springframework.bundle.spring.context/spring-context@2.5",
|
||||
"spring-context@2.5": "pkg:maven/org.springframework/spring-context@2.5",
|
||||
"spring-core@2.5": "pkg:maven/org.springframework/spring-core@2.5",
|
||||
"spring-dao@1.2.9": "pkg:maven/spring-dao/spring-dao@1.2.9",
|
||||
"spring-jdbc@1.2.9": "pkg:maven/spring-jdbc/spring-jdbc@1.2.9",
|
||||
"spring-dao@1.2.9": "pkg:maven/org.springframework/spring-dao@1.2.9",
|
||||
"spring-jdbc@1.2.9": "pkg:maven/org.springframework/spring-jdbc@1.2.9",
|
||||
"spring-web@2.5": "pkg:maven/org.springframework/spring-web@2.5",
|
||||
"ssh-slaves@0.14": "pkg:maven/org.jvnet.hudson.plugins/ssh-slaves@0.14",
|
||||
"stapler-adjunct-timeline@1.2": "pkg:maven/org.kohsuke.stapler/stapler-adjunct-timeline@1.2",
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestPackageOwnershipRelationships(t *testing.T) {
|
||||
t.Fatalf("unable to decode json doc: %+v", err)
|
||||
}
|
||||
|
||||
if len(doc.Relationships) == 0 {
|
||||
if len(doc.ArtifactRelationships) == 0 {
|
||||
t.Errorf("expected to find relationships between packages but found none")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user