mirror of
https://github.com/anchore/syft.git
synced 2026-10-27 12:32:26 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1530ef354f | ||
|
|
cdac2245b5 | ||
|
|
8dba4c33dd | ||
|
|
b81c9805dc | ||
|
|
ba55963104 | ||
|
|
8c91605541 |
@@ -4,7 +4,7 @@ inputs:
|
||||
go-version:
|
||||
description: "Go version to install"
|
||||
required: true
|
||||
default: "1.18.x"
|
||||
default: "1.19.x"
|
||||
use-go-cache:
|
||||
description: "Restore go cache"
|
||||
required: true
|
||||
|
||||
@@ -4,7 +4,7 @@ set -eu
|
||||
ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX")
|
||||
TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX")
|
||||
|
||||
trap "cp ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
trap "cp -p ${ORIGINAL_STATE_DIR}/* ./ && git update-index -q --refresh && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT
|
||||
|
||||
# capturing original state of files...
|
||||
cp go.mod go.sum "${ORIGINAL_STATE_DIR}"
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git diff-index HEAD --
|
||||
git --no-pager diff
|
||||
echo "there are uncommitted changes, please commit them before running this check"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success=true
|
||||
|
||||
if ! make generate-json-schema; then
|
||||
echo "Generating json schema failed"
|
||||
success=false
|
||||
fi
|
||||
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
git diff-index HEAD --
|
||||
git --no-pager diff
|
||||
echo "JSON schema drift detected!"
|
||||
success=false
|
||||
fi
|
||||
|
||||
if ! $success; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.18.x"
|
||||
GO_VERSION: "1.19.x"
|
||||
GO_STABLE_VERSION: true
|
||||
|
||||
jobs:
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Contributing to Syft
|
||||
[#](#) Contributing to Syft
|
||||
|
||||
If you are looking to contribute to this project and want to open a GitHub pull request ("PR"), there are a few guidelines of what we are looking for in patches. Make sure you go through this document and ensure that your code proposal is aligned.
|
||||
|
||||
@@ -10,7 +10,7 @@ Before you can contribute to Syft, you need to configure your development enviro
|
||||
|
||||
You will need to install Go. The version on https://go.dev works best, using the system golang doesn't always work the way you might expect.
|
||||
|
||||
At the time of writing, Go 1.19 does not work correctly with Syft. Please use Go 1.18 for now.
|
||||
Refer to the go.mod file in the root of this repo for the recommended version of Go to install.
|
||||
|
||||
You will also need Docker. There's no reason the system packages shouldn't work, but we used the official Docker package. You can find instructions for installing Docker in Debian [here](https://docs.docker.com/engine/install/debian/).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ GOLANGCILINT_VERSION := v1.50.1
|
||||
GOSIMPORTS_VERSION := v0.3.5
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.5.1
|
||||
GORELEASER_VERSION := v1.14.1
|
||||
GORELEASER_VERSION := v1.15.0
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
@@ -132,10 +132,7 @@ check-go-mod-tidy:
|
||||
|
||||
check-json-schema-drift:
|
||||
$(call title,Ensure there is no drift between the JSON schema and the code)
|
||||
@git diff-index --quiet HEAD -- || (echo "there are uncommitted changes, please commit them before running this check" && false)
|
||||
@make generate-json-schema || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
|
||||
@git diff-index --quiet HEAD -- || (echo "$(RED)$(BOLD)JSON schema drift detected!$(RESET)" && false)
|
||||
|
||||
@.github/scripts/json-schema-drift-check.sh
|
||||
|
||||
## Testing targets #################################
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ const (
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions, ao *options.AttestOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "attest --output [FORMAT] <IMAGE>",
|
||||
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||
@@ -50,11 +49,17 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po
|
||||
},
|
||||
}
|
||||
|
||||
// syft attest is an enhancment of the packages command, so it should have the same flags
|
||||
// syft attest is an enhancement of the packages command, so it should have the same flags
|
||||
err := po.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// syft attest has its own options not included as part of the packages command
|
||||
err = ao.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
|
||||
return sBytes, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
@@ -131,9 +132,18 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()}
|
||||
if app.Attest.Key != "" {
|
||||
args = append(args, "--key", app.Attest.Key)
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
if app.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
// bus adapter for ui to hook into stdout via an os pipe
|
||||
r, w, err := os.Pipe()
|
||||
|
||||
@@ -45,12 +45,13 @@ func New() (*cobra.Command, error) {
|
||||
// we also need the command to have information about the `root` options because of this alias
|
||||
ro := &options.RootOptions{}
|
||||
po := &options.PackagesOptions{}
|
||||
ao := &options.AttestOptions{}
|
||||
packagesCmd := Packages(v, app, ro, po)
|
||||
|
||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||
poweruserCmd := PowerUser(v, app, ro)
|
||||
convertCmd := Convert(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po, ao)
|
||||
|
||||
// rootCmd is currently an alias for the packages command
|
||||
rootCmd := &cobra.Command{
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/anchore/syft/syft"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, "")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -30,7 +30,9 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
sbom, _, err := syft.Decode(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type AttestOptions struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
var _ Interface = (*AttestOptions)(nil)
|
||||
|
||||
func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Key, "key", "k", "", "the key to use for the attestation")
|
||||
return bindAttestConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.18
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
@@ -31,7 +31,7 @@ require (
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spdx/tools-golang v0.4.0
|
||||
github.com/spdx/tools-golang v0.5.0-rc1
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
@@ -69,6 +69,7 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
|
||||
@@ -138,6 +138,8 @@ github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 h1:imgMA0gN0TZx7
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8/go.mod h1:+gPap4jha079qzRTUaehv+UZ6sSdaNwkH0D3b6zhTuk=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb/go.mod h1:DmTY2Mfcv38hsHbG78xMiTDdxFtkHpgYNVDPsF2TgHk=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 h1:VzprUTpc0vW0nnNKJfJieyH/TZ9UYAnTZs5/gHTdAe8=
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04/go.mod h1:6dK64g27Qi1qGQZ67gFmBFvEHScy0/C8qhQhNe5B5pQ=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
@@ -1046,8 +1048,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
||||
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.4.0 h1:jdhnW8zYelURCbYTphiviFKZkWu51in0E4A1KT2csP0=
|
||||
github.com/spdx/tools-golang v0.4.0/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1 h1:ooCSe48QatlidqEFd+nSI308tyeNTR6NJvauUj3ApX8=
|
||||
github.com/spdx/tools-golang v0.5.0-rc1/go.mod h1:LI6onw172PdO57Ob/hgnLDD4Y2PMnroeNT3wO/2WJJI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
|
||||
@@ -47,6 +47,7 @@ type Application struct {
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type attest struct {
|
||||
Key string `yaml:"key" json:"key" mapstructure:"key"`
|
||||
Password string `yaml:"password" json:"password" mapstructure:"password"`
|
||||
}
|
||||
|
||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("attest.key", "")
|
||||
v.SetDefault("attest.password", "")
|
||||
}
|
||||
@@ -8,8 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
@@ -23,7 +22,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
spdxVersion = "SPDX-2.3"
|
||||
noAssertion = "NOASSERTION"
|
||||
)
|
||||
|
||||
@@ -40,11 +38,11 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
// for the primary package purpose field:
|
||||
// https://spdx.github.io/spdx-spec/v2.3/package-information/#724-primary-package-purpose-field
|
||||
documentDescribesRelationship := &spdx.Relationship{
|
||||
RefA: common.DocElementID{
|
||||
RefA: spdx.DocElementID{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
Relationship: string(DescribesRelationship),
|
||||
RefB: common.DocElementID{
|
||||
RefB: spdx.DocElementID{
|
||||
ElementRefID: "DOCUMENT",
|
||||
},
|
||||
RelationshipComment: "",
|
||||
@@ -55,11 +53,11 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
return &spdx.Document{
|
||||
// 6.1: SPDX Version; should be in the format "SPDX-x.x"
|
||||
// Cardinality: mandatory, one
|
||||
SPDXVersion: spdxVersion,
|
||||
SPDXVersion: spdx.Version,
|
||||
|
||||
// 6.2: Data License; should be "CC0-1.0"
|
||||
// Cardinality: mandatory, one
|
||||
DataLicense: "CC0-1.0",
|
||||
DataLicense: spdx.DataLicense,
|
||||
|
||||
// 6.3: SPDX Identifier; should be "DOCUMENT" to represent mandatory identifier of SPDXRef-DOCUMENT
|
||||
// Cardinality: mandatory, one
|
||||
@@ -104,7 +102,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
// 6.8: Creators: may have multiple keys for Person, Organization
|
||||
// and/or Tool
|
||||
// Cardinality: mandatory, one or many
|
||||
Creators: []common.Creator{
|
||||
Creators: []spdx.Creator{
|
||||
{
|
||||
Creator: "Anchore, Inc",
|
||||
CreatorType: "Organization",
|
||||
@@ -129,7 +127,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
|
||||
}
|
||||
}
|
||||
|
||||
func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||
func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
|
||||
id := ""
|
||||
if p, ok := identifiable.(pkg.Package); ok {
|
||||
id = SanitizeElementID(fmt.Sprintf("Package-%+v-%s-%s", p.Type, p.Name, p.ID()))
|
||||
@@ -137,7 +135,7 @@ func toSPDXID(identifiable artifact.Identifiable) common.ElementID {
|
||||
id = string(identifiable.ID())
|
||||
}
|
||||
// NOTE: the spdx libraries prepend SPDXRef-, so we don't do it here
|
||||
return common.ElementID(id)
|
||||
return spdx.ElementID(id)
|
||||
}
|
||||
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
@@ -313,9 +311,9 @@ func toPackages(catalog *pkg.Catalog, sbom sbom.SBOM) (results []*spdx.Package)
|
||||
return results
|
||||
}
|
||||
|
||||
func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
|
||||
filesAnalyzed := false
|
||||
var checksums []common.Checksum
|
||||
var checksums []spdx.Checksum
|
||||
switch meta := p.Metadata.(type) {
|
||||
// we generate digest for some Java packages
|
||||
// spdx.github.io/spdx-spec/package-information/#710-package-checksum-field
|
||||
@@ -325,8 +323,8 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
filesAnalyzed = true
|
||||
for _, digest := range meta.ArchiveDigests {
|
||||
algo := strings.ToUpper(digest.Algorithm)
|
||||
checksums = append(checksums, common.Checksum{
|
||||
Algorithm: common.ChecksumAlgorithm(algo),
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: spdx.ChecksumAlgorithm(algo),
|
||||
Value: digest.Value,
|
||||
})
|
||||
}
|
||||
@@ -339,20 +337,20 @@ func toPackageChecksums(p pkg.Package) ([]common.Checksum, bool) {
|
||||
break
|
||||
}
|
||||
algo = strings.ToUpper(algo)
|
||||
checksums = append(checksums, common.Checksum{
|
||||
Algorithm: common.ChecksumAlgorithm(algo),
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: spdx.ChecksumAlgorithm(algo),
|
||||
Value: hexStr,
|
||||
})
|
||||
}
|
||||
return checksums, filesAnalyzed
|
||||
}
|
||||
|
||||
func toPackageOriginator(p pkg.Package) *common.Originator {
|
||||
func toPackageOriginator(p pkg.Package) *spdx.Originator {
|
||||
kind, originator := Originator(p)
|
||||
if kind == "" || originator == "" {
|
||||
return nil
|
||||
}
|
||||
return &common.Originator{
|
||||
return &spdx.Originator{
|
||||
Originator: originator,
|
||||
OriginatorType: kind,
|
||||
}
|
||||
@@ -386,11 +384,11 @@ func toRelationships(relationships []artifact.Relationship) (result []*spdx.Rela
|
||||
}
|
||||
|
||||
result = append(result, &spdx.Relationship{
|
||||
RefA: common.DocElementID{
|
||||
RefA: spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(r.From),
|
||||
},
|
||||
Relationship: string(relationshipType),
|
||||
RefB: common.DocElementID{
|
||||
RefB: spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(r.To),
|
||||
},
|
||||
RelationshipComment: comment,
|
||||
@@ -462,10 +460,10 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) {
|
||||
return results
|
||||
}
|
||||
|
||||
func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||
checksums = make([]common.Checksum, 0, len(digests))
|
||||
func toFileChecksums(digests []file.Digest) (checksums []spdx.Checksum) {
|
||||
checksums = make([]spdx.Checksum, 0, len(digests))
|
||||
for _, digest := range digests {
|
||||
checksums = append(checksums, common.Checksum{
|
||||
checksums = append(checksums, spdx.Checksum{
|
||||
Algorithm: toChecksumAlgorithm(digest.Algorithm),
|
||||
Value: digest.Value,
|
||||
})
|
||||
@@ -473,9 +471,9 @@ func toFileChecksums(digests []file.Digest) (checksums []common.Checksum) {
|
||||
return checksums
|
||||
}
|
||||
|
||||
func toChecksumAlgorithm(algorithm string) common.ChecksumAlgorithm {
|
||||
func toChecksumAlgorithm(algorithm string) spdx.ChecksumAlgorithm {
|
||||
// this needs to be an uppercase version of our algorithm
|
||||
return common.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||
return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm))
|
||||
}
|
||||
|
||||
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
@@ -517,7 +515,7 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
// f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */
|
||||
// see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field
|
||||
// the above link contains the SPDX algorithm for a package verification code
|
||||
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *common.PackageVerificationCode {
|
||||
func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
|
||||
// key off of the contains relationship;
|
||||
// spdx validator will fail if a package claims to contain a file but no sha1 provided
|
||||
// if a sha1 for a file is provided then the validator will fail if the package does not have
|
||||
@@ -558,7 +556,7 @@ func newPackageVerificationCode(p pkg.Package, sbom sbom.SBOM) *common.PackageVe
|
||||
//nolint:gosec
|
||||
hasher := sha1.New()
|
||||
_, _ = hasher.Write([]byte(b.String()))
|
||||
return &common.PackageVerificationCode{
|
||||
return &spdx.PackageVerificationCode{
|
||||
// 7.9.1: Package Verification Code Value
|
||||
// Cardinality: mandatory, one
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
|
||||
@@ -4,8 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -21,7 +20,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkg pkg.Package
|
||||
expected []common.Checksum
|
||||
expected []spdx.Checksum
|
||||
filesAnalyzed bool
|
||||
}{
|
||||
{
|
||||
@@ -39,7 +38,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA1",
|
||||
Value: "1234",
|
||||
@@ -57,7 +56,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
ArchiveDigests: []file.Digest{},
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
{
|
||||
@@ -67,7 +66,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Version: "1.0.0",
|
||||
Language: pkg.Java,
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
{
|
||||
@@ -81,7 +80,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
H1Digest: "h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=",
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA256",
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
@@ -97,7 +96,7 @@ func Test_toPackageChecksums(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Metadata: struct{}{},
|
||||
},
|
||||
expected: []common.Checksum{},
|
||||
expected: []spdx.Checksum{},
|
||||
filesAnalyzed: false,
|
||||
},
|
||||
}
|
||||
@@ -229,7 +228,7 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
digests []file.Digest
|
||||
expected []common.Checksum
|
||||
expected []spdx.Checksum
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
@@ -246,7 +245,7 @@ func Test_toFileChecksums(t *testing.T) {
|
||||
Value: "meh",
|
||||
},
|
||||
},
|
||||
expected: []common.Checksum{
|
||||
expected: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA256",
|
||||
Value: "deadbeefcafe",
|
||||
@@ -275,8 +274,8 @@ func Test_fileIDsForPackage(t *testing.T) {
|
||||
FileSystemID: "nowhere",
|
||||
}
|
||||
|
||||
docElementId := func(identifiable artifact.Identifiable) common.DocElementID {
|
||||
return common.DocElementID{
|
||||
docElementId := func(identifiable artifact.Identifiable) spdx.DocElementID {
|
||||
return spdx.DocElementID{
|
||||
ElementRefID: toSPDXID(identifiable),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
|
||||
@@ -3,8 +3,7 @@ package spdxhelpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx/common"
|
||||
spdx "github.com/spdx/tools-golang/spdx/v2_3"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -246,9 +245,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA256,
|
||||
Algorithm: spdx.SHA256,
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
},
|
||||
},
|
||||
@@ -267,9 +266,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA1,
|
||||
Algorithm: spdx.SHA1,
|
||||
Value: "f5f1c0b4ad2e0dfa6f79eaaaa3586411925c16f61702208ddd4bad2fc17dc47c",
|
||||
},
|
||||
},
|
||||
@@ -288,9 +287,9 @@ func TestH1Digest(t *testing.T) {
|
||||
RefType: "purl",
|
||||
},
|
||||
},
|
||||
PackageChecksums: []common.Checksum{
|
||||
PackageChecksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: common.SHA256,
|
||||
Algorithm: spdx.SHA256,
|
||||
Value: "",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
spdx "github.com/spdx/tools-golang/json"
|
||||
"github.com/spdx/tools-golang/json"
|
||||
|
||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (s *sbom.SBOM, err error) {
|
||||
doc, err := spdx.Load2_3(reader)
|
||||
doc, err := json.Read(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode spdx-json: %w", err)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spdx/tools-golang/tvloader"
|
||||
"github.com/spdx/tools-golang/tagvalue"
|
||||
|
||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func decoder(reader io.Reader) (*sbom.SBOM, error) {
|
||||
doc, err := tvloader.Load2_3(reader)
|
||||
doc, err := tagvalue.Read(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode spdx-tag-value: %w", err)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package spdxtagvalue
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/spdx/tools-golang/tvsaver"
|
||||
"github.com/spdx/tools-golang/tagvalue"
|
||||
|
||||
"github.com/anchore/syft/syft/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -11,5 +11,5 @@ import (
|
||||
|
||||
func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
model := spdxhelpers.ToFormatModel(s)
|
||||
return tvsaver.Save2_3(model, output)
|
||||
return tagvalue.Write(model, output)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ type pathFilterFn func(string, os.FileInfo) bool
|
||||
// directoryResolver implements path and content access for the directory data source.
|
||||
type directoryResolver struct {
|
||||
path string
|
||||
base string
|
||||
currentWdRelativeToRoot string
|
||||
currentWd string
|
||||
fileTree *filetree.FileTree
|
||||
@@ -47,7 +48,7 @@ type directoryResolver struct {
|
||||
errPaths map[string]error
|
||||
}
|
||||
|
||||
func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryResolver, error) {
|
||||
func newDirectoryResolver(root string, base string, pathFilters ...pathFilterFn) (*directoryResolver, error) {
|
||||
currentWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get CWD: %w", err)
|
||||
@@ -64,6 +65,18 @@ func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryR
|
||||
return nil, fmt.Errorf("could not evaluate root=%q symlinks: %w", root, err)
|
||||
}
|
||||
|
||||
cleanBase := ""
|
||||
if base != "" {
|
||||
cleanBase, err = filepath.EvalSymlinks(base)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not evaluate base=%q symlinks: %w", base, err)
|
||||
}
|
||||
cleanBase, err = filepath.Abs(cleanBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var currentWdRelRoot string
|
||||
if path.IsAbs(cleanRoot) {
|
||||
currentWdRelRoot, err = filepath.Rel(cleanCWD, cleanRoot)
|
||||
@@ -76,6 +89,7 @@ func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryR
|
||||
|
||||
resolver := directoryResolver{
|
||||
path: cleanRoot,
|
||||
base: cleanBase,
|
||||
currentWd: cleanCWD,
|
||||
currentWdRelativeToRoot: currentWdRelRoot,
|
||||
fileTree: filetree.NewFileTree(),
|
||||
@@ -244,10 +258,25 @@ func (r directoryResolver) addSymlinkToIndex(p string, info os.FileInfo) (string
|
||||
return "", fmt.Errorf("unable to readlink for path=%q: %w", p, err)
|
||||
}
|
||||
|
||||
// note: if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to resolve it relative to the directory
|
||||
// in question (e.g. resolve to /dev/fd/2)
|
||||
if !filepath.IsAbs(linkTarget) {
|
||||
linkTarget = filepath.Join(filepath.Dir(p), linkTarget)
|
||||
if filepath.IsAbs(linkTarget) {
|
||||
// if the link is absolute (e.g, /bin/ls -> /bin/busybox) we need to
|
||||
// resolve relative to the root of the base directory
|
||||
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget))
|
||||
} else {
|
||||
// if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to
|
||||
// resolve it relative to the directory in question (e.g. resolve to
|
||||
// /dev/fd/2)
|
||||
if r.base == "" {
|
||||
linkTarget = filepath.Join(filepath.Dir(p), linkTarget)
|
||||
} else {
|
||||
// if the base is set, then we first need to resolve the link,
|
||||
// before finding it's location in the base
|
||||
dir, err := filepath.Rel(r.base, filepath.Dir(p))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to resolve relative path for path=%q: %w", p, err)
|
||||
}
|
||||
linkTarget = filepath.Join(r.base, filepath.Clean(filepath.Join("/", dir, linkTarget)))
|
||||
}
|
||||
}
|
||||
|
||||
ref, err := r.fileTree.AddSymLink(file.Path(p), file.Path(linkTarget))
|
||||
@@ -336,14 +365,17 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
}
|
||||
|
||||
// we should be resolving symlinks and preserving this information as a VirtualPath to the real file
|
||||
evaluatedPath, err := filepath.EvalSymlinks(userStrPath)
|
||||
exists, ref, err := r.fileTree.File(file.Path(userStrPath), filetree.FollowBasenameLinks)
|
||||
if err != nil {
|
||||
log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: why not use stored metadata?
|
||||
fileMeta, err := os.Stat(evaluatedPath)
|
||||
fileMeta, err := os.Stat(string(ref.RealPath))
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// note: there are other kinds of errors other than os.ErrNotExist that may be given that is platform
|
||||
// specific, but essentially hints at the same overall problem (that the path does not exist). Such an
|
||||
@@ -354,7 +386,7 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
// invalid paths. This logging statement is meant to raise IO or permissions related problems.
|
||||
var pathErr *os.PathError
|
||||
if !errors.As(err, &pathErr) {
|
||||
log.Warnf("path is not valid (%s): %+v", evaluatedPath, err)
|
||||
log.Warnf("path is not valid (%s): %+v", ref.RealPath, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -368,15 +400,12 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
userStrPath = windowsToPosix(userStrPath)
|
||||
}
|
||||
|
||||
exists, ref, err := r.fileTree.File(file.Path(userStrPath), filetree.FollowBasenameLinks)
|
||||
if err == nil && exists {
|
||||
loc := NewVirtualLocationFromDirectory(
|
||||
r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
|
||||
r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root
|
||||
*ref,
|
||||
)
|
||||
references = append(references, loc)
|
||||
}
|
||||
loc := NewVirtualLocationFromDirectory(
|
||||
r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
|
||||
r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root
|
||||
*ref,
|
||||
)
|
||||
references = append(references, loc)
|
||||
}
|
||||
|
||||
return references, nil
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.relativeRoot)
|
||||
resolver, err := newDirectoryResolver(c.relativeRoot, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
@@ -111,7 +111,7 @@ func TestDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) {
|
||||
absRoot, err := filepath.Abs(c.relativeRoot)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := newDirectoryResolver(absRoot)
|
||||
resolver, err := newDirectoryResolver(absRoot, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
@@ -172,7 +172,7 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.root)
|
||||
resolver, err := newDirectoryResolver(c.root, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
hasPath := resolver.HasPath(c.input)
|
||||
@@ -220,7 +220,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByPath(c.input...)
|
||||
assert.NoError(t, err)
|
||||
@@ -233,7 +233,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/image-symlinks/file*")
|
||||
assert.NoError(t, err)
|
||||
@@ -242,7 +242,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/*.txt")
|
||||
assert.NoError(t, err)
|
||||
@@ -250,7 +250,7 @@ func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt")
|
||||
assert.NoError(t, err)
|
||||
@@ -277,7 +277,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(test.fixture)
|
||||
@@ -300,7 +300,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) {
|
||||
|
||||
func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) {
|
||||
// let's make certain that "dev/place" is not ignored, since it is not "/dev/place"
|
||||
resolver, err := newDirectoryResolver("test-fixtures/system_paths/target")
|
||||
resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", "")
|
||||
assert.NoError(t, err)
|
||||
// ensure the correct filter function is wired up by default
|
||||
expectedFn := reflect.ValueOf(isUnallowableFileType)
|
||||
@@ -431,7 +431,7 @@ func Test_isUnallowableFileType(t *testing.T) {
|
||||
|
||||
func Test_directoryResolver_index(t *testing.T) {
|
||||
// note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex
|
||||
r, err := newDirectoryResolver("test-fixtures/system_paths/target")
|
||||
r, err := newDirectoryResolver("test-fixtures/system_paths/target", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get indexed dir resolver: %+v", err)
|
||||
}
|
||||
@@ -608,7 +608,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixturePath, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(test.fixturePath)
|
||||
resolver, err := newDirectoryResolver(test.fixturePath, "")
|
||||
assert.NoError(t, err)
|
||||
locations, err := resolver.FilesByMIMEType(test.mimeType)
|
||||
assert.NoError(t, err)
|
||||
@@ -621,7 +621,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_IndexingNestedSymLinks(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that we can get the real path
|
||||
@@ -674,7 +674,7 @@ func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) {
|
||||
return strings.HasSuffix(path, string(filepath.Separator)+"readme")
|
||||
}
|
||||
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", filterFn)
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "", filterFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
// the path to the real file is PRUNED from the index, so we should NOT expect a location returned
|
||||
@@ -694,7 +694,7 @@ func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that we can get the real path
|
||||
@@ -712,7 +712,7 @@ func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_RootViaSymlink(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
locations, err := resolver.FilesByPath("./file1.txt")
|
||||
@@ -753,7 +753,7 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r, err := newDirectoryResolver(".")
|
||||
r, err := newDirectoryResolver(".", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := r.FileContentsByLocation(test.location)
|
||||
@@ -819,7 +819,7 @@ func Test_isUnixSystemRuntimePath(t *testing.T) {
|
||||
|
||||
func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
locations, err := resolver.FilesByGlob("**/file.target")
|
||||
@@ -853,7 +853,7 @@ func Test_IncludeRootPathInIndex(t *testing.T) {
|
||||
return path != "/"
|
||||
}
|
||||
|
||||
resolver, err := newDirectoryResolver("/", filterFn)
|
||||
resolver, err := newDirectoryResolver("/", "", filterFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, ref, err := resolver.fileTree.File(file.Path("/"))
|
||||
@@ -870,7 +870,7 @@ func TestDirectoryResolver_indexPath(t *testing.T) {
|
||||
tempFile, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := newDirectoryResolver(tempFile.Name())
|
||||
resolver, err := newDirectoryResolver(tempFile.Name(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("filtering path with nil os.FileInfo", func(t *testing.T) {
|
||||
@@ -885,3 +885,76 @@ func TestDirectoryResolver_indexPath(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
root string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "should find the base file",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./base",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow a link with a pivoted root",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./foo",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow a relative link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./bar",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow an absolute link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./baz",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow an absolute link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./sub/link",
|
||||
expected: []string{
|
||||
"sub/item",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow chained pivoted link",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./chain",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.root, c.root)
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, refs, len(c.expected))
|
||||
s := strset.New()
|
||||
for _, actual := range refs {
|
||||
s.Add(actual.RealPath)
|
||||
}
|
||||
assert.ElementsMatch(t, c.expected, s.List())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ type Metadata struct {
|
||||
Scheme Scheme // the source data scheme type (directory or image)
|
||||
ImageMetadata ImageMetadata // all image info (image only)
|
||||
Path string // the root path to be cataloged (directory only)
|
||||
Base string // the base path to be cataloged (directory only)
|
||||
Name string
|
||||
}
|
||||
|
||||
+24
-1
@@ -32,6 +32,7 @@ type Source struct {
|
||||
Metadata Metadata
|
||||
directoryResolver *directoryResolver `hash:"ignore"`
|
||||
path string
|
||||
base string
|
||||
mutex *sync.Mutex
|
||||
Exclusions []string `hash:"ignore"`
|
||||
}
|
||||
@@ -252,6 +253,11 @@ func NewFromDirectory(path string) (Source, error) {
|
||||
return NewFromDirectoryWithName(path, "")
|
||||
}
|
||||
|
||||
// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
|
||||
func NewFromDirectoryRoot(path string) (Source, error) {
|
||||
return NewFromDirectoryRootWithName(path, "")
|
||||
}
|
||||
|
||||
// NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
|
||||
func NewFromDirectoryWithName(path string, name string) (Source, error) {
|
||||
s := Source{
|
||||
@@ -267,6 +273,23 @@ func NewFromDirectoryWithName(path string, name string) (Source, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
|
||||
func NewFromDirectoryRootWithName(path string, name string) (Source, error) {
|
||||
s := Source{
|
||||
mutex: &sync.Mutex{},
|
||||
Metadata: Metadata{
|
||||
Name: name,
|
||||
Scheme: DirectoryScheme,
|
||||
Path: path,
|
||||
Base: path,
|
||||
},
|
||||
path: path,
|
||||
base: path,
|
||||
}
|
||||
s.SetID()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewFromFile creates a new source object tailored to catalog a file.
|
||||
func NewFromFile(path string) (Source, func()) {
|
||||
return NewFromFileWithName(path, "")
|
||||
@@ -428,7 +451,7 @@ func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolver, err := newDirectoryResolver(s.path, exclusionFunctions...)
|
||||
resolver, err := newDirectoryResolver(s.path, s.base, exclusionFunctions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create directory resolver: %w", err)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestSetID(t *testing.T) {
|
||||
Path: "test-fixtures/image-simple",
|
||||
},
|
||||
},
|
||||
expected: artifact.ID("14b60020c4f9955"),
|
||||
expected: artifact.ID("1b0dc351e6577b01"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../base
|
||||
@@ -0,0 +1 @@
|
||||
/../../base
|
||||
@@ -0,0 +1 @@
|
||||
/foo
|
||||
@@ -0,0 +1 @@
|
||||
/base
|
||||
@@ -0,0 +1 @@
|
||||
../sub/item
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -9,13 +10,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type conversion struct {
|
||||
To string
|
||||
From string
|
||||
}
|
||||
|
||||
func TestConvertCmdFlags(t *testing.T) {
|
||||
commonAssertions := []traitAssertion{
|
||||
assertions := []traitAssertion{
|
||||
func(tb testing.TB, stdout, _ string, _ int) {
|
||||
tb.Helper()
|
||||
if len(stdout) < 1000 {
|
||||
@@ -26,52 +22,48 @@ func TestConvertCmdFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
conversions []conversion
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
to string
|
||||
from string
|
||||
template string
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
name: "syft-format convertable to spdx-json",
|
||||
conversions: []conversion{
|
||||
{To: "syft-json", From: "spdx-json"},
|
||||
{To: "syft-json", From: "cyclonedx-json"},
|
||||
{To: "spdx-json", From: "syft-json"},
|
||||
{To: "spdx-json", From: "cyclonedx-json"},
|
||||
{To: "cyclonedx-json", From: "syft-json"},
|
||||
{To: "cyclonedx-json", From: "spdx-json"},
|
||||
},
|
||||
assertions: commonAssertions,
|
||||
},
|
||||
{to: "syft-json", from: "spdx-json"},
|
||||
{to: "syft-json", from: "cyclonedx-json"},
|
||||
{to: "spdx-json", from: "syft-json"},
|
||||
{to: "template", from: "syft-json", template: "test-fixtures/csv.template"},
|
||||
{to: "spdx-json", from: "cyclonedx-json"},
|
||||
{to: "cyclonedx-json", from: "syft-json"},
|
||||
{to: "cyclonedx-json", from: "spdx-json"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, c := range test.conversions {
|
||||
sbomArgs := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", c.From}
|
||||
cmd, stdout, stderr := runSyft(t, test.env, sbomArgs...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Fatalf("failure executing syft creating an sbom")
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
return
|
||||
}
|
||||
t.Run(fmt.Sprintf("from %s to %s", test.from, test.to), func(t *testing.T) {
|
||||
sbomArgs := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", test.from}
|
||||
cmd, stdout, stderr := runSyft(t, test.env, sbomArgs...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
t.Fatalf("failure executing syft creating an sbom")
|
||||
return
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
sbomFile := filepath.Join(tempDir, "sbom.json")
|
||||
require.NoError(t, os.WriteFile(sbomFile, []byte(stdout), 0666))
|
||||
tempDir := t.TempDir()
|
||||
sbomFile := filepath.Join(tempDir, "sbom.json")
|
||||
require.NoError(t, os.WriteFile(sbomFile, []byte(stdout), 0666))
|
||||
|
||||
convertArgs := []string{"convert", sbomFile, "-o", c.To}
|
||||
cmd, stdout, stderr = runSyft(t, test.env, convertArgs...)
|
||||
for _, traitFn := range test.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
convertArgs := []string{"convert", sbomFile, "-o", test.to}
|
||||
if test.template != "" {
|
||||
convertArgs = append(convertArgs, "--template", test.template)
|
||||
}
|
||||
cmd, stdout, stderr = runSyft(t, test.env, convertArgs...)
|
||||
for _, traitFn := range assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -618,6 +618,9 @@ func AttestationStartedHandler(ctx context.Context, fr *frame.Frame, event party
|
||||
text := s.Text()
|
||||
if strings.Contains(text, "tlog entry created with index") {
|
||||
tlogEntry = text
|
||||
} else {
|
||||
// no tlog entry create so user used personal PKI
|
||||
tlogEntry = "signed attestation using provided key"
|
||||
}
|
||||
_, err = line.Write([]byte(fmt.Sprintf(" %s %s", auxInfoFormat.Sprintf("░░"), text)))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user