mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
feat: add --from
flag, refactor source providers (#2610)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
This commit is contained in:
parent
928511ea0f
commit
a978966cad
72 changed files with 1035 additions and 1080 deletions
33
README.md
33
README.md
|
@ -122,7 +122,8 @@ syft <image> --scope all-layers
|
|||
|
||||
### Supported sources
|
||||
|
||||
Syft can generate an SBOM from a variety of sources:
|
||||
Syft can generate an SBOM from a variety of sources including images, files, directories, and archives. Syft will attempt to
|
||||
determine the type of source based on provided input, for example:
|
||||
|
||||
```bash
|
||||
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
|
||||
|
@ -135,26 +136,24 @@ syft path/to/image.sif
|
|||
syft path/to/dir
|
||||
```
|
||||
|
||||
Sources can be explicitly provided with a scheme:
|
||||
To explicitly specify the source behavior, use the `--from` flag. Allowable options are:
|
||||
|
||||
```
|
||||
docker:yourrepo/yourimage:tag use images from the Docker daemon
|
||||
podman:yourrepo/yourimage:tag use images from the Podman daemon
|
||||
containerd:yourrepo/yourimage:tag use images from the Containerd daemon
|
||||
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
|
||||
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir:path/to/yourproject read directly from a path on disk (any directory)
|
||||
file:path/to/yourproject/file read directly from a path on disk (any single file)
|
||||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
docker use images from the Docker daemon
|
||||
podman use images from the Podman daemon
|
||||
containerd use images from the Containerd daemon
|
||||
docker-archive use a tarball from disk for archives created from "docker save"
|
||||
oci-archive use a tarball from disk for OCI archives (from Skopeo or otherwise)
|
||||
oci-dir read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
|
||||
singularity read directly from a Singularity Image Format (SIF) container on disk
|
||||
dir read directly from a path on disk (any directory)
|
||||
file read directly from a path on disk (any single file)
|
||||
registry pull image directly from a registry (no container runtime required)
|
||||
```
|
||||
If a source is not provided and Syft identifies the input as a potential image reference, Syft will attempt to resolve it using:
|
||||
the Docker, Podman, and Containerd daemons followed by direct registry access, in that order.
|
||||
|
||||
If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
|
||||
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.
|
||||
|
||||
|
||||
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
|
||||
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](#configuration) for more details).
|
||||
|
||||
|
||||
### File selection
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal"
|
||||
|
@ -26,7 +27,6 @@ import (
|
|||
"github.com/anchore/syft/syft/format/spdxtagvalue"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -247,7 +247,11 @@ func predicateType(outputName string) string {
|
|||
}
|
||||
|
||||
func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
|
||||
src, err := getSource(opts, userInput, onlyContainerImages)
|
||||
if len(opts.From) > 1 || (len(opts.From) == 1 && opts.From[0] != stereoscope.RegistryTag) {
|
||||
return nil, fmt.Errorf("attest requires use of an OCI registry directly, one or more of the specified sources is unsupported: %v", opts.From)
|
||||
}
|
||||
|
||||
src, err := getSource(ctx, opts, userInput, stereoscope.RegistryTag)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -273,13 +277,6 @@ func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opt
|
|||
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
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/cmd/syft/internal/options"
|
||||
"github.com/anchore/syft/cmd/syft/internal/ui"
|
||||
|
@ -24,6 +26,7 @@ import (
|
|||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -162,14 +165,23 @@ func validateArgs(cmd *cobra.Command, args []string, error string) error {
|
|||
return cobra.MaximumNArgs(1)(cmd, args)
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
|
||||
writer, err := opts.SBOMWriter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
src, err := getSource(&opts.Catalog, userInput)
|
||||
sources := opts.From
|
||||
if len(sources) == 0 {
|
||||
// extract a scheme if it matches any provider tag; this is a holdover for compatibility, using the --from flag is recommended
|
||||
explicitSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceProviderTags()...)
|
||||
if explicitSource != "" {
|
||||
sources = append(sources, explicitSource)
|
||||
userInput = newUserInput
|
||||
}
|
||||
}
|
||||
|
||||
src, err := getSource(ctx, &opts.Catalog, userInput, sources...)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -199,23 +211,21 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use
|
|||
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.Source.Image.DefaultPullSource,
|
||||
},
|
||||
)
|
||||
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
|
||||
}
|
||||
}
|
||||
func getSource(ctx context.Context, opts *options.Catalog, userInput string, sources ...string) (source.Source, error) {
|
||||
cfg := syft.DefaultGetSourceConfig().
|
||||
WithRegistryOptions(opts.Registry.ToOptions()).
|
||||
WithAlias(source.Alias{
|
||||
Name: opts.Source.Name,
|
||||
Version: opts.Source.Version,
|
||||
}).
|
||||
WithExcludeConfig(source.ExcludeConfig{
|
||||
Paths: opts.Exclusions,
|
||||
}).
|
||||
WithBasePath(opts.Source.BasePath).
|
||||
WithSources(sources...).
|
||||
WithDefaultImagePullSource(opts.Source.Image.DefaultPullSource)
|
||||
|
||||
var err error
|
||||
var platform *image.Platform
|
||||
|
||||
if opts.Platform != "" {
|
||||
|
@ -223,29 +233,22 @@ func getSource(opts *options.Catalog, userInput string, filters ...func(*source.
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid platform: %w", err)
|
||||
}
|
||||
cfg = cfg.WithPlatform(platform)
|
||||
}
|
||||
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if opts.Source.File.Digests != nil {
|
||||
hashers, err := file.Hashers(opts.Source.File.Digests...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
|
||||
}
|
||||
cfg = cfg.WithDigestAlgorithms(hashers...)
|
||||
}
|
||||
|
||||
src, err := syft.GetSource(ctx, userInput, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
|
||||
return nil, fmt.Errorf("could not determine source: %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.Source.BasePath,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if userInput == "power-user" {
|
||||
bus.Notify("Note: the 'power-user' command has been removed.")
|
||||
|
@ -445,3 +448,7 @@ func getHintPhrase(expErr task.ErrInvalidExpression) string {
|
|||
func trimOperation(x string) string {
|
||||
return strings.TrimLeft(x, "+-")
|
||||
}
|
||||
|
||||
func allSourceProviderTags() []string {
|
||||
return collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All("", nil)...).Tags()
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ type Catalog struct {
|
|||
|
||||
// configuration for the source (the subject being analyzed)
|
||||
Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
From []string `yaml:"from" json:"from" mapstructure:"from"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
|
@ -163,6 +164,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
|
|||
flags.StringVarP(&cfg.Scope, "scope", "s",
|
||||
fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))
|
||||
|
||||
flags.StringArrayVarP(&cfg.From, "from", "",
|
||||
"specify the source behavior to use (e.g. docker, registry, oci-dir, ...)")
|
||||
|
||||
flags.StringVarP(&cfg.Platform, "platform", "",
|
||||
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
|
||||
|
||||
|
@ -220,6 +224,8 @@ func (cfg *Catalog) PostLoad() error {
|
|||
return out
|
||||
}
|
||||
|
||||
cfg.From = flatten(cfg.From)
|
||||
|
||||
cfg.Catalogers = flatten(cfg.Catalogers)
|
||||
cfg.DefaultCatalogers = flatten(cfg.DefaultCatalogers)
|
||||
cfg.SelectCatalogers = flatten(cfg.SelectCatalogers)
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
type sourceConfig struct {
|
||||
|
@ -25,12 +27,13 @@ type imageSource struct {
|
|||
}
|
||||
|
||||
func defaultSourceConfig() sourceConfig {
|
||||
var digests []string
|
||||
for _, alg := range sourceproviders.DefaultConfig().DigestAlgorithms {
|
||||
digests = append(digests, alg.String())
|
||||
}
|
||||
return sourceConfig{
|
||||
File: fileSource{
|
||||
Digests: []string{"sha256"},
|
||||
},
|
||||
Image: imageSource{
|
||||
DefaultPullSource: "",
|
||||
Digests: digests,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,15 +22,11 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
|||
tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
|
||||
|
||||
// get the source object for the image
|
||||
userInput := "docker-archive:" + tarPath
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(b, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
|
||||
require.NoError(b, err)
|
||||
|
||||
b.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(b, theSource.Close())
|
||||
})
|
||||
|
||||
// build the SBOM
|
||||
|
|
|
@ -34,17 +34,13 @@ func catalogFixtureImageWithConfig(t *testing.T, fixtureImageName string, cfg *s
|
|||
// get the fixture image tar file
|
||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
|
||||
// get the source to build an SBOM against
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithSources("docker-archive"))
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(t, theSource.Close())
|
||||
})
|
||||
|
||||
s, err := syft.CreateSBOM(context.Background(), theSource, cfg)
|
||||
|
@ -71,14 +67,10 @@ func catalogDirectoryWithConfig(t *testing.T, dir string, cfg *syft.CreateSBOMCo
|
|||
cfg.CatalogerSelection = cfg.CatalogerSelection.WithDefaults(pkgcataloging.DirectoryTag)
|
||||
|
||||
// get the source to build an sbom against
|
||||
userInput := "dir:" + dir
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
theSource, err := syft.GetSource(context.Background(), dir, syft.DefaultGetSourceConfig().WithSources("dir"))
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
require.NoError(t, theSource.Close())
|
||||
})
|
||||
|
||||
// build the SBOM
|
||||
|
|
|
@ -44,17 +44,7 @@ func imageReference() string {
|
|||
func getSource(input string) source.Source {
|
||||
fmt.Println("detecting source type for input:", input, "...")
|
||||
|
||||
detection, err := source.Detect(input,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -37,17 +37,7 @@ func imageReference() string {
|
|||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
detection, err := source.Detect(input,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -41,17 +41,7 @@ func imageReference() string {
|
|||
}
|
||||
|
||||
func getSource(input string) source.Source {
|
||||
detection, err := source.Detect(input,
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
src, err := syft.GetSource(context.Background(), input, nil)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -28,18 +33,18 @@ import (
|
|||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
detection, err := source.Detect(
|
||||
imageReference(),
|
||||
source.DetectConfig{
|
||||
DefaultImageSource: "docker",
|
||||
},
|
||||
)
|
||||
userInput := imageReference()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// parse the scheme against the known set of schemes
|
||||
schemeSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceTags()...)
|
||||
|
||||
// set up the GetSourceConfig
|
||||
getSourceCfg := syft.DefaultGetSourceConfig()
|
||||
if schemeSource != "" {
|
||||
getSourceCfg = getSourceCfg.WithSources(schemeSource)
|
||||
userInput = newUserInput
|
||||
}
|
||||
|
||||
src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
src, err := syft.GetSource(context.Background(), userInput, getSourceCfg)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -60,3 +65,7 @@ func imageReference() string {
|
|||
}
|
||||
return defaultImage
|
||||
}
|
||||
|
||||
func allSourceTags() []string {
|
||||
return collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All("", nil)...).Tags()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image/oci"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -16,22 +18,15 @@ import (
|
|||
const defaultImage = "alpine:3.19"
|
||||
|
||||
func main() {
|
||||
platform, err := image.NewPlatform("linux/amd64")
|
||||
// using oci.Registry causes the lookup to always use the registry, there are several other "Source" options here
|
||||
img, err := stereoscope.GetImageFromSource(context.Background(), imageReference(), oci.Registry, stereoscope.WithPlatform("linux/amd64"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
src, err := source.NewFromStereoscopeImage(
|
||||
source.StereoscopeImageConfig{
|
||||
Reference: imageReference(),
|
||||
From: image.OciRegistrySource, // always use the registry, there are several other "Source" options here
|
||||
Platform: platform,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: imageReference(),
|
||||
})
|
||||
|
||||
// Show a basic description of the source to the screen
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
|
|
4
go.mod
4
go.mod
|
@ -16,7 +16,7 @@ require (
|
|||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240216182029-6171ee21e1d5
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240221144950-cf0e754f5b56
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
|
@ -80,6 +80,8 @@ require (
|
|||
modernc.org/sqlite v1.29.2
|
||||
)
|
||||
|
||||
require github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||
|
|
6
go.sum
6
go.sum
|
@ -97,6 +97,8 @@ github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65 h1:u9XrEabKlGPsrmRvAE
|
|||
github.com/anchore/clio v0.0.0-20240209204744-cb94e40a4f65/go.mod h1:8Jr7CjmwFVcBPtkJdTpaAGHimoGJGfbExypjzOu87Og=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A=
|
||||
github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a/go.mod h1:ubLFmlsv8/DFUQrZwY5syT5/8Er3ugSr4rDFwHsE3hg=
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb h1:iDMnx6LIjtjZ46C0akqveX83WFzhpTD3eqOthawb5vU=
|
||||
|
@ -109,8 +111,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
|||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426 h1:agoiZchSf1Nnnos1azwIg5hk5Ao9TzZNBD9++AChGEg=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20240202171727-877e1747d426/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240216182029-6171ee21e1d5 h1:o//fhRcSpOYHC/xG/HiI6ddtSMiRgHlB96xJQXZawZM=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240216182029-6171ee21e1d5/go.mod h1:o0TqYkefad6kIPtmbigFKss7P48z4bjd8Vp5Wklbf3Y=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240221144950-cf0e754f5b56 h1:iHvTXZA+qEozPGRRuW1Mv7r7w2fHeJdzWDx+YsSIbyg=
|
||||
github.com/anchore/stereoscope v0.0.2-0.20240221144950-cf0e754f5b56/go.mod h1:evQiJMQG56Z7/L5uhA8kfhhjF6ESJUZzUH9ms6bQ2Co=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
|
|
|
@ -345,9 +345,9 @@ func (c *CreateSBOMConfig) Create(ctx context.Context, src source.Source) (*sbom
|
|||
|
||||
func findDefaultTag(src source.Description) (string, error) {
|
||||
switch m := src.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
return pkgcataloging.ImageTag, nil
|
||||
case source.FileSourceMetadata, source.DirectorySourceMetadata:
|
||||
case source.FileMetadata, source.DirectoryMetadata:
|
||||
return pkgcataloging.DirectoryTag, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unable to determine default cataloger tag for source type=%T", m)
|
||||
|
|
|
@ -62,15 +62,15 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||
}
|
||||
|
||||
imgSrc := source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{},
|
||||
Metadata: source.ImageMetadata{},
|
||||
}
|
||||
|
||||
dirSrc := source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
}
|
||||
|
||||
fileSrc := source.Description{
|
||||
Metadata: source.FileSourceMetadata{},
|
||||
Metadata: source.FileMetadata{},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
@ -437,21 +437,21 @@ func Test_findDefaultTag(t *testing.T) {
|
|||
{
|
||||
name: "image",
|
||||
src: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{},
|
||||
Metadata: source.ImageMetadata{},
|
||||
},
|
||||
want: pkgcataloging.ImageTag,
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
src: source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag,
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
src: source.Description{
|
||||
Metadata: source.FileSourceMetadata{},
|
||||
Metadata: source.FileMetadata{},
|
||||
},
|
||||
want: pkgcataloging.DirectoryTag, // not a mistake...
|
||||
},
|
||||
|
|
|
@ -17,6 +17,8 @@ import (
|
|||
intFile "github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[file.Coordinates][]file.Digest {
|
||||
|
@ -77,7 +79,7 @@ func TestDigestsCataloger(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
c := NewCataloger(test.digests)
|
||||
|
||||
src, err := source.NewFromDirectoryPath("test-fixtures/last/")
|
||||
src, err := directorysource.NewFromPath("test-fixtures/last/")
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
|
@ -96,10 +98,9 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) {
|
|||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("could not create source: %+v", err)
|
||||
}
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
if err != nil {
|
||||
|
@ -169,8 +170,9 @@ func TestFileDigestCataloger_GivenCoordinates(t *testing.T) {
|
|||
|
||||
c := NewCataloger([]crypto.Hash{crypto.SHA256})
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
require.NoError(t, err)
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func TestFileMetadataCataloger(t *testing.T) {
|
||||
|
@ -21,8 +22,9 @@ func TestFileMetadataCataloger(t *testing.T) {
|
|||
|
||||
c := NewCataloger()
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
require.NoError(t, err)
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
@ -159,8 +161,9 @@ func TestFileMetadataCataloger_GivenCoordinates(t *testing.T) {
|
|||
|
||||
c := NewCataloger()
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
require.NoError(t, err)
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func Test_allRegularFiles(t *testing.T) {
|
||||
|
@ -28,8 +30,9 @@ func Test_allRegularFiles(t *testing.T) {
|
|||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
|
||||
|
||||
s, err := source.NewFromStereoscopeImageObject(img, testImage, nil)
|
||||
require.NoError(t, err)
|
||||
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: testImage,
|
||||
})
|
||||
|
||||
r, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
@ -42,7 +45,7 @@ func Test_allRegularFiles(t *testing.T) {
|
|||
{
|
||||
name: "directory",
|
||||
setup: func() file.Resolver {
|
||||
s, err := source.NewFromDirectoryPath("test-fixtures/symlinked-root/nested/link-root")
|
||||
s, err := directorysource.NewFromPath("test-fixtures/symlinked-root/nested/link-root")
|
||||
require.NoError(t, err)
|
||||
r, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -208,7 +208,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
|||
}
|
||||
|
||||
func toBomProperties(srcMetadata source.Description) *[]cyclonedx.Property {
|
||||
metadata, ok := srcMetadata.Metadata.(source.StereoscopeImageSourceMetadata)
|
||||
metadata, ok := srcMetadata.Metadata.(source.ImageMetadata)
|
||||
if ok {
|
||||
props := helpers.EncodeProperties(metadata.Labels, "syft:image:labels")
|
||||
return &props
|
||||
|
@ -220,7 +220,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
|||
name := srcMetadata.Name
|
||||
version := srcMetadata.Version
|
||||
switch metadata := srcMetadata.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
if name == "" {
|
||||
name = metadata.UserInput
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
|||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
if name == "" {
|
||||
name = metadata.Path
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
|
|||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
if name == "" {
|
||||
name = metadata.Path
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ func Test_toBomDescriptor(t *testing.T) {
|
|||
name: "test-image",
|
||||
version: "1.0.0",
|
||||
srcMetadata: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
Labels: map[string]string{
|
||||
"key1": "value1",
|
||||
},
|
||||
|
|
|
@ -181,7 +181,7 @@ func toRootPackage(s source.Description) *spdx.Package {
|
|||
purpose := ""
|
||||
var checksums []spdx.Checksum
|
||||
switch m := s.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
prefix = prefixImage
|
||||
purpose = spdxPrimaryPurposeContainer
|
||||
|
||||
|
@ -211,11 +211,11 @@ func toRootPackage(s source.Description) *spdx.Package {
|
|||
}
|
||||
}
|
||||
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
prefix = prefixDirectory
|
||||
purpose = spdxPrimaryPurposeFile
|
||||
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
prefix = prefixFile
|
||||
purpose = spdxPrimaryPurposeFile
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ func Test_toFormatModel(t *testing.T) {
|
|||
Source: source.Description{
|
||||
Name: "alpine",
|
||||
Version: "sha256:d34db33f",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "alpine:latest",
|
||||
ManifestDigest: "sha256:d34db33f",
|
||||
},
|
||||
|
@ -106,7 +106,7 @@ func Test_toFormatModel(t *testing.T) {
|
|||
in: sbom.SBOM{
|
||||
Source: source.Description{
|
||||
Name: "some/directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/directory",
|
||||
},
|
||||
},
|
||||
|
@ -170,7 +170,7 @@ func Test_toFormatModel(t *testing.T) {
|
|||
Source: source.Description{
|
||||
Name: "path/to/some.file",
|
||||
Version: "sha256:d34db33f",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "path/to/some.file",
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
|
|
|
@ -146,7 +146,7 @@ func containerSource(p *spdx.Package) source.Description {
|
|||
ID: id,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: container,
|
||||
ID: id,
|
||||
Layers: nil, // TODO handle formats with nested layer packages like Tern and K8s BOM tool
|
||||
|
@ -187,7 +187,7 @@ func fileSource(p *spdx.Package) source.Description {
|
|||
func fileSourceMetadata(p *spdx.Package) (any, string) {
|
||||
version := p.PackageVersion
|
||||
|
||||
m := source.FileSourceMetadata{
|
||||
m := source.FileMetadata{
|
||||
Path: p.PackageName,
|
||||
}
|
||||
// if this is a Syft SBOM, we might have output a digest as the version
|
||||
|
@ -206,7 +206,7 @@ func fileSourceMetadata(p *spdx.Package) (any, string) {
|
|||
}
|
||||
|
||||
func directorySourceMetadata(p *spdx.Package) (any, string) {
|
||||
return source.DirectorySourceMetadata{
|
||||
return source.DirectoryMetadata{
|
||||
Path: p.PackageName,
|
||||
Base: "",
|
||||
}, p.PackageVersion
|
||||
|
@ -229,15 +229,15 @@ func extractSourceFromNamespace(ns string) source.Description {
|
|||
switch p {
|
||||
case helpers.InputFile:
|
||||
return source.Description{
|
||||
Metadata: source.FileSourceMetadata{},
|
||||
Metadata: source.FileMetadata{},
|
||||
}
|
||||
case helpers.InputImage:
|
||||
return source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{},
|
||||
Metadata: source.ImageMetadata{},
|
||||
}
|
||||
case helpers.InputDirectory:
|
||||
return source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -199,15 +199,15 @@ func TestExtractSourceFromNamespaces(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
namespace: "https://anchore.com/syft/file/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.FileSourceMetadata{},
|
||||
expected: source.FileMetadata{},
|
||||
},
|
||||
{
|
||||
namespace: "https://anchore.com/syft/image/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.StereoscopeImageSourceMetadata{},
|
||||
expected: source.ImageMetadata{},
|
||||
},
|
||||
{
|
||||
namespace: "https://anchore.com/syft/dir/d42b01d0-7325-409b-b03f-74082935c4d3",
|
||||
expected: source.DirectorySourceMetadata{},
|
||||
expected: source.DirectoryMetadata{},
|
||||
},
|
||||
{
|
||||
namespace: "https://another-host/blob/123",
|
||||
|
@ -460,7 +460,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
|||
name: "image source",
|
||||
source: source.Description{
|
||||
ID: "DocumentRoot-Image-some-image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
ID: "DocumentRoot-Image-some-image",
|
||||
UserInput: "some-image:some-tag",
|
||||
ManifestDigest: "sha256:ab8b83234bc28f28d8e",
|
||||
|
@ -476,7 +476,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
|||
source: source.Description{
|
||||
ID: "DocumentRoot-Directory-.",
|
||||
Name: ".",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: ".",
|
||||
},
|
||||
},
|
||||
|
@ -488,7 +488,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
|||
source: source.Description{
|
||||
ID: "DocumentRoot-Directory-my-app",
|
||||
Name: "my-app",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "my-app",
|
||||
},
|
||||
},
|
||||
|
@ -499,7 +499,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
|||
name: "file source",
|
||||
source: source.Description{
|
||||
ID: "DocumentRoot-File-my-app.exe",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "my-app.exe",
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
|
|
|
@ -116,16 +116,16 @@ func toPath(s source.Description, p pkg.Package) string {
|
|||
}
|
||||
packagePath = strings.TrimPrefix(packagePath, "/")
|
||||
switch metadata := s.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
image := strings.ReplaceAll(metadata.UserInput, ":/", "//")
|
||||
return fmt.Sprintf("%s:/%s", image, packagePath)
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
path := trimRelative(metadata.Path)
|
||||
if isArchive(metadata.Path) {
|
||||
return fmt.Sprintf("%s:/%s", path, packagePath)
|
||||
}
|
||||
return path
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
path := trimRelative(metadata.Path)
|
||||
if path != "" {
|
||||
return fmt.Sprintf("%s/%s", path, packagePath)
|
||||
|
|
|
@ -22,7 +22,7 @@ func sbomFixture() sbom.SBOM {
|
|||
Name: "syft",
|
||||
},
|
||||
Source: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "ubuntu:18.04",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
|
@ -150,27 +150,27 @@ func Test_toGithubModel(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "current directory",
|
||||
metadata: source.DirectorySourceMetadata{Path: "."},
|
||||
metadata: source.DirectoryMetadata{Path: "."},
|
||||
testPath: "etc",
|
||||
},
|
||||
{
|
||||
name: "relative directory",
|
||||
metadata: source.DirectorySourceMetadata{Path: "./artifacts"},
|
||||
metadata: source.DirectoryMetadata{Path: "./artifacts"},
|
||||
testPath: "artifacts/etc",
|
||||
},
|
||||
{
|
||||
name: "absolute directory",
|
||||
metadata: source.DirectorySourceMetadata{Path: "/artifacts"},
|
||||
metadata: source.DirectoryMetadata{Path: "/artifacts"},
|
||||
testPath: "/artifacts/etc",
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
metadata: source.FileSourceMetadata{Path: "./executable"},
|
||||
metadata: source.FileMetadata{Path: "./executable"},
|
||||
testPath: "executable",
|
||||
},
|
||||
{
|
||||
name: "archive",
|
||||
metadata: source.FileSourceMetadata{Path: "./archive.tar.gz"},
|
||||
metadata: source.FileMetadata{Path: "./archive.tar.gz"},
|
||||
testPath: "archive.tar.gz:/etc",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
|
|||
ID: "",
|
||||
// TODO: can we decode alias name-version somehow? (it isn't be encoded in the first place yet)
|
||||
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: c.Name,
|
||||
ID: c.BOMRef,
|
||||
ManifestDigest: c.Version,
|
||||
|
@ -235,7 +235,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
|
|||
// TODO: this is lossy... we can't know if this is a file or a directory
|
||||
return source.Description{
|
||||
ID: "",
|
||||
Metadata: source.FileSourceMetadata{Path: c.Name},
|
||||
Metadata: source.FileMetadata{Path: c.Name},
|
||||
}
|
||||
}
|
||||
return source.Description{}
|
||||
|
|
|
@ -10,11 +10,11 @@ func DocumentName(src source.Description) string {
|
|||
}
|
||||
|
||||
switch metadata := src.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
return metadata.UserInput
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
return metadata.Path
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
return metadata.Path
|
||||
default:
|
||||
return "unknown"
|
||||
|
|
|
@ -23,7 +23,7 @@ func Test_DocumentName(t *testing.T) {
|
|||
{
|
||||
name: "image",
|
||||
srcMetadata: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "image-repo/name:tag",
|
||||
ID: "id",
|
||||
ManifestDigest: "digest",
|
||||
|
@ -34,14 +34,14 @@ func Test_DocumentName(t *testing.T) {
|
|||
{
|
||||
name: "directory",
|
||||
srcMetadata: source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{Path: "some/path/to/place"},
|
||||
Metadata: source.DirectoryMetadata{Path: "some/path/to/place"},
|
||||
},
|
||||
expected: "some/path/to/place",
|
||||
},
|
||||
{
|
||||
name: "file",
|
||||
srcMetadata: source.Description{
|
||||
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
|
||||
Metadata: source.FileMetadata{Path: "some/path/to/place"},
|
||||
},
|
||||
expected: "some/path/to/place",
|
||||
},
|
||||
|
@ -49,7 +49,7 @@ func Test_DocumentName(t *testing.T) {
|
|||
name: "named",
|
||||
srcMetadata: source.Description{
|
||||
Name: "some/name",
|
||||
Metadata: source.FileSourceMetadata{Path: "some/path/to/place"},
|
||||
Metadata: source.FileMetadata{Path: "some/path/to/place"},
|
||||
},
|
||||
expected: "some/name",
|
||||
},
|
||||
|
|
|
@ -27,11 +27,11 @@ func DocumentNamespace(name string, src source.Description, desc sbom.Descriptor
|
|||
name = cleanName(name)
|
||||
input := "unknown-source-type"
|
||||
switch src.Metadata.(type) {
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
input = InputImage
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
input = InputDirectory
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
input = InputFile
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ func Test_documentNamespace(t *testing.T) {
|
|||
name: "image",
|
||||
inputName: "my-name",
|
||||
src: source.Description{
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "image-repo/name:tag",
|
||||
ID: "id",
|
||||
ManifestDigest: "digest",
|
||||
|
@ -37,7 +37,7 @@ func Test_documentNamespace(t *testing.T) {
|
|||
name: "directory",
|
||||
inputName: "my-name",
|
||||
src: source.Description{
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path/to/place",
|
||||
},
|
||||
},
|
||||
|
@ -47,7 +47,7 @@ func Test_documentNamespace(t *testing.T) {
|
|||
name: "file",
|
||||
inputName: "my-name",
|
||||
src: source.Description{
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path/to/place",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
)
|
||||
|
||||
func DirectoryInput(t testing.TB, dir string) sbom.SBOM {
|
||||
|
@ -22,8 +22,8 @@ func DirectoryInput(t testing.TB, dir string) sbom.SBOM {
|
|||
|
||||
require.NoError(t, os.MkdirAll(path, 0755))
|
||||
|
||||
src, err := source.NewFromDirectory(
|
||||
source.DirectoryConfig{
|
||||
src, err := directorysource.New(
|
||||
directorysource.Config{
|
||||
Path: path,
|
||||
Base: dir,
|
||||
},
|
||||
|
@ -63,8 +63,8 @@ func DirectoryInputWithAuthorField(t testing.TB) sbom.SBOM {
|
|||
|
||||
require.NoError(t, os.MkdirAll(path, 0755))
|
||||
|
||||
src, err := source.NewFromDirectory(
|
||||
source.DirectoryConfig{
|
||||
src, err := directorysource.New(
|
||||
directorysource.Config{
|
||||
Path: path,
|
||||
Base: dir,
|
||||
},
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
|
@ -16,7 +15,7 @@ import (
|
|||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBOM {
|
||||
|
@ -42,8 +41,9 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
|
|||
// this is a hard coded value that is not given by the fixture helper and must be provided manually
|
||||
img.Metadata.ManifestDigest = "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
|
||||
src, err := source.NewFromStereoscopeImageObject(img, "user-image-input", nil)
|
||||
assert.NoError(t, err)
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: "user-image-input",
|
||||
})
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestSPDXJSONSPDXIDs(t *testing.T) {
|
|||
Relationships: nil,
|
||||
Source: source.Description{
|
||||
Name: "foobar/baz", // in this case, foobar is used as the spdx document name
|
||||
Metadata: source.DirectorySourceMetadata{},
|
||||
Metadata: source.DirectoryMetadata{},
|
||||
},
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
|
|
|
@ -263,7 +263,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
|||
ID: "some-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "/some-file-source-path",
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
|
|
|
@ -260,7 +260,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
|||
},
|
||||
Source: source.Description{
|
||||
ID: "c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-image-input",
|
||||
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
|
||||
ManifestDigest: "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
|
@ -269,7 +269,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
|||
"stereoscope-fixture-image-simple:85066c51088bdd274f7a89e99e00490f666c49e72ffc955707cd6e18f0e22c5b",
|
||||
},
|
||||
Size: 38,
|
||||
Layers: []source.StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Digest: "sha256:3de16c5b8659a2e8d888b8ded8427be7a5686a3c8c4e4dd30de20f362827285b",
|
||||
|
|
|
@ -89,7 +89,7 @@ func extractPreSchemaV9Metadata(t string, target []byte) (interface{}, error) {
|
|||
cleanTarget = string(target)
|
||||
}
|
||||
|
||||
return source.DirectorySourceMetadata{
|
||||
return source.DirectoryMetadata{
|
||||
Path: cleanTarget,
|
||||
}, nil
|
||||
|
||||
|
@ -99,12 +99,12 @@ func extractPreSchemaV9Metadata(t string, target []byte) (interface{}, error) {
|
|||
cleanTarget = string(target)
|
||||
}
|
||||
|
||||
return source.FileSourceMetadata{
|
||||
return source.FileMetadata{
|
||||
Path: cleanTarget,
|
||||
}, nil
|
||||
|
||||
case "image":
|
||||
var payload source.StereoscopeImageSourceMetadata
|
||||
var payload source.ImageMetadata
|
||||
if err := json.Unmarshal(target, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
|||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
//Base: "/nope", // note: should be ignored entirely
|
||||
},
|
||||
|
@ -67,14 +67,14 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
|||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "alpine:3.10",
|
||||
ID: "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
ManifestDigest: "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
Tags: []string{},
|
||||
Size: 5576169,
|
||||
Layers: []source.StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Digest: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
|
@ -124,7 +124,7 @@ func TestSource_UnmarshalJSON(t *testing.T) {
|
|||
expected: &Source{
|
||||
ID: "foobar",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "/var/lib/foo/go.mod",
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
|
@ -188,7 +188,7 @@ func TestSource_UnmarshalJSON_PreSchemaV9(t *testing.T) {
|
|||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
},
|
||||
},
|
||||
|
@ -204,7 +204,7 @@ func TestSource_UnmarshalJSON_PreSchemaV9(t *testing.T) {
|
|||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "/var/lib/foo",
|
||||
},
|
||||
},
|
||||
|
@ -239,14 +239,14 @@ func TestSource_UnmarshalJSON_PreSchemaV9(t *testing.T) {
|
|||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "alpine:3.10",
|
||||
ID: "sha256:e7b300aee9f9bf3433d32bc9305bfdd22183beb59d933b48d77ab56ba53a197a",
|
||||
ManifestDigest: "sha256:e515aad2ed234a5072c4d2ef86a1cb77d5bfe4b11aa865d9214875734c4eeb3c",
|
||||
MediaType: "application/vnd.docker.distribution.manifest.v2+json",
|
||||
Tags: []string{},
|
||||
Size: 5576169,
|
||||
Layers: []source.StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
Digest: "sha256:9fb3aa2f8b8023a4bebbf92aa567caf88e38e969ada9f0ac12643b2847391635",
|
||||
|
@ -288,7 +288,7 @@ func TestSource_UnmarshalJSON_PreSchemaV9(t *testing.T) {
|
|||
expectedSource: &Source{
|
||||
ID: "foobar",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "/var/lib/foo/go.mod",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -303,7 +303,7 @@ func toSourceModel(src source.Description) model.Source {
|
|||
Metadata: src.Metadata,
|
||||
}
|
||||
|
||||
if metadata, ok := src.Metadata.(source.StereoscopeImageSourceMetadata); ok {
|
||||
if metadata, ok := src.Metadata.(source.ImageMetadata); ok {
|
||||
// ensure that empty collections are not shown as null
|
||||
if metadata.RepoDigests == nil {
|
||||
metadata.RepoDigests = []string{}
|
||||
|
|
|
@ -26,7 +26,7 @@ func Test_toSourceModel_IgnoreBase(t *testing.T) {
|
|||
name: "directory",
|
||||
src: source.Description{
|
||||
ID: "test-id",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -59,7 +59,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
ID: "test-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -69,7 +69,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -81,7 +81,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
ID: "test-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -92,7 +92,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -105,7 +105,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
ID: "test-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -117,7 +117,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -133,7 +133,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
name: "directory - no name/version",
|
||||
src: source.Description{
|
||||
ID: "test-id",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -141,7 +141,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
expected: model.Source{
|
||||
ID: "test-id",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -151,7 +151,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
name: "file - no name/version",
|
||||
src: source.Description{
|
||||
ID: "test-id",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -160,7 +160,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
expected: model.Source{
|
||||
ID: "test-id",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -171,7 +171,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
name: "image - no name/version",
|
||||
src: source.Description{
|
||||
ID: "test-id",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -181,7 +181,7 @@ func Test_toSourceModel(t *testing.T) {
|
|||
expected: model.Source{
|
||||
ID: "test-id",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
|
|
@ -35,7 +35,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -44,7 +44,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
ID: "the-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -57,7 +57,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -67,7 +67,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
ID: "the-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -81,7 +81,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -92,7 +92,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
ID: "the-id",
|
||||
Name: "some-name",
|
||||
Version: "some-version",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -107,14 +107,14 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
src: model.Source{
|
||||
ID: "the-id",
|
||||
Type: "directory",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
},
|
||||
expected: &source.Description{
|
||||
ID: "the-id",
|
||||
Metadata: source.DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: "some/path",
|
||||
Base: "some/base",
|
||||
},
|
||||
|
@ -125,7 +125,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
src: model.Source{
|
||||
ID: "the-id",
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -133,7 +133,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
},
|
||||
expected: &source.Description{
|
||||
ID: "the-id",
|
||||
Metadata: source.FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: "some/path",
|
||||
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
|
||||
MIMEType: "text/plain",
|
||||
|
@ -145,7 +145,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
src: model.Source{
|
||||
ID: "the-id",
|
||||
Type: "image",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -154,7 +154,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
|||
},
|
||||
expected: &source.Description{
|
||||
ID: "the-id",
|
||||
Metadata: source.StereoscopeImageSourceMetadata{
|
||||
Metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
ManifestDigest: "digest...",
|
||||
|
@ -178,7 +178,7 @@ func Test_idsHaveChanged(t *testing.T) {
|
|||
s := toSyftModel(model.Document{
|
||||
Source: model.Source{
|
||||
Type: "file",
|
||||
Metadata: source.FileSourceMetadata{Path: "some/path"},
|
||||
Metadata: source.FileMetadata{Path: "some/path"},
|
||||
},
|
||||
Artifacts: []model.Package{
|
||||
{
|
||||
|
|
|
@ -38,11 +38,11 @@ func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
|
|||
w.Init(writer, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||
|
||||
switch metadata := s.Source.Metadata.(type) {
|
||||
case source.DirectorySourceMetadata:
|
||||
case source.DirectoryMetadata:
|
||||
fmt.Fprintf(w, "[Path: %s]\n", metadata.Path)
|
||||
case source.FileSourceMetadata:
|
||||
case source.FileMetadata:
|
||||
fmt.Fprintf(w, "[Path: %s]\n", metadata.Path)
|
||||
case source.StereoscopeImageSourceMetadata:
|
||||
case source.ImageMetadata:
|
||||
fmt.Fprintln(w, "[Image]")
|
||||
|
||||
for idx, l := range metadata.Layers {
|
||||
|
|
101
syft/get_source.go
Normal file
101
syft/get_source.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package syft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// GetSource uses all of Syft's known source providers to attempt to resolve the user input to a usable source.Source
|
||||
func GetSource(ctx context.Context, userInput string, cfg *GetSourceConfig) (source.Source, error) {
|
||||
if cfg == nil {
|
||||
cfg = DefaultGetSourceConfig()
|
||||
}
|
||||
|
||||
providers, err := cfg.getProviders(userInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
var fileNotfound error
|
||||
|
||||
// call each source provider until we find a valid source
|
||||
for _, p := range providers {
|
||||
src, err := p.Provide(ctx)
|
||||
if err != nil {
|
||||
err = eachError(err, func(err error) error {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
fileNotfound = err
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if src != nil {
|
||||
// if we have a non-image type and platform is specified, it's an error
|
||||
if cfg.SourceProviderConfig.Platform != nil {
|
||||
meta := src.Describe().Metadata
|
||||
switch meta.(type) {
|
||||
case *source.ImageMetadata, source.ImageMetadata:
|
||||
default:
|
||||
return src, fmt.Errorf("platform specified with non-image source")
|
||||
}
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
}
|
||||
|
||||
if fileNotfound != nil {
|
||||
errs = append([]error{fileNotfound}, errs...)
|
||||
}
|
||||
return nil, sourceError(userInput, errs...)
|
||||
}
|
||||
|
||||
func sourceError(userInput string, errs ...error) error {
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return fmt.Errorf("an error occurred attempting to resolve '%s': %w", userInput, errs[0])
|
||||
}
|
||||
errorTexts := ""
|
||||
for _, e := range errs {
|
||||
errorTexts += fmt.Sprintf("\n - %s", e)
|
||||
}
|
||||
return fmt.Errorf("errors occurred attempting to resolve '%s':%s", userInput, errorTexts)
|
||||
}
|
||||
|
||||
func eachError(err error, fn func(error) error) error {
|
||||
out := fn(err)
|
||||
// unwrap singly wrapped errors
|
||||
if e, ok := err.(interface {
|
||||
Unwrap() error
|
||||
}); ok {
|
||||
wrapped := e.Unwrap()
|
||||
got := eachError(wrapped, fn)
|
||||
// return the outer error if received the same wrapped error
|
||||
if errors.Is(got, wrapped) {
|
||||
return err
|
||||
}
|
||||
return got
|
||||
}
|
||||
// unwrap errors from errors.Join
|
||||
if errs, ok := err.(interface {
|
||||
Unwrap() []error
|
||||
}); ok {
|
||||
for _, e := range errs.Unwrap() {
|
||||
e = eachError(e, fn)
|
||||
if e != nil {
|
||||
out = errors.Join(out, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
91
syft/get_source_config.go
Normal file
91
syft/get_source_config.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package syft
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/sourceproviders"
|
||||
)
|
||||
|
||||
type GetSourceConfig struct {
|
||||
// SourceProviderConfig may optionally be provided to be used when constructing the default set of source providers, unused if All specified
|
||||
SourceProviderConfig *sourceproviders.Config
|
||||
|
||||
// Sources is an explicit list of source names to use, in order, to attempt to locate a source
|
||||
Sources []string
|
||||
|
||||
// DefaultImagePullSource will cause a particular image pull source to be used as the first pull source, followed by other pull sources
|
||||
DefaultImagePullSource string
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithAlias(alias source.Alias) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithAlias(alias)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithRegistryOptions(registryOptions *image.RegistryOptions) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithRegistryOptions(registryOptions)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithPlatform(platform *image.Platform) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithPlatform(platform)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithExcludeConfig(excludeConfig source.ExcludeConfig) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithExcludeConfig(excludeConfig)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithDigestAlgorithms(algorithms ...crypto.Hash) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithDigestAlgorithms(algorithms...)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithBasePath(basePath string) *GetSourceConfig {
|
||||
c.SourceProviderConfig = c.SourceProviderConfig.WithBasePath(basePath)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithSources(sources ...string) *GetSourceConfig {
|
||||
c.Sources = sources
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) WithDefaultImagePullSource(defaultImagePullSource string) *GetSourceConfig {
|
||||
c.DefaultImagePullSource = defaultImagePullSource
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *GetSourceConfig) getProviders(userInput string) ([]source.Provider, error) {
|
||||
providers := collections.TaggedValueSet[source.Provider]{}.Join(sourceproviders.All(userInput, c.SourceProviderConfig)...)
|
||||
|
||||
// if the "default image pull source" is set, we move this as the first pull source
|
||||
if c.DefaultImagePullSource != "" {
|
||||
base := providers.Remove(sourceproviders.PullTag)
|
||||
pull := providers.Select(sourceproviders.PullTag)
|
||||
def := pull.Select(c.DefaultImagePullSource)
|
||||
if len(def) == 0 {
|
||||
return nil, fmt.Errorf("invalid DefaultImagePullSource: %s; available values are: %v", c.DefaultImagePullSource, pull.Tags())
|
||||
}
|
||||
providers = base.Join(def...).Join(pull...)
|
||||
}
|
||||
|
||||
// narrow the sources to those explicitly requested generally by a user
|
||||
if len(c.Sources) > 0 {
|
||||
// select the explicitly provided sources, in order
|
||||
providers = providers.Select(c.Sources...)
|
||||
}
|
||||
|
||||
return providers.Values(), nil
|
||||
}
|
||||
|
||||
func DefaultGetSourceConfig() *GetSourceConfig {
|
||||
return &GetSourceConfig{
|
||||
SourceProviderConfig: sourceproviders.DefaultConfig(),
|
||||
}
|
||||
}
|
|
@ -6,5 +6,5 @@ import "github.com/anchore/syft/syft/source"
|
|||
|
||||
// AllTypes returns a list of all source metadata types that syft supports (that are represented in the source.Description.Metadata field).
|
||||
func AllTypes() []any {
|
||||
return []any{source.DirectorySourceMetadata{}, source.FileSourceMetadata{}, source.StereoscopeImageSourceMetadata{}}
|
||||
return []any{source.DirectoryMetadata{}, source.FileMetadata{}, source.ImageMetadata{}}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
)
|
||||
|
||||
var jsonNameFromType = map[reflect.Type][]string{
|
||||
reflect.TypeOf(source.DirectorySourceMetadata{}): {"directory", "dir"},
|
||||
reflect.TypeOf(source.FileSourceMetadata{}): {"file"},
|
||||
reflect.TypeOf(source.StereoscopeImageSourceMetadata{}): {"image"},
|
||||
reflect.TypeOf(source.DirectoryMetadata{}): {"directory", "dir"},
|
||||
reflect.TypeOf(source.FileMetadata{}): {"file"},
|
||||
reflect.TypeOf(source.ImageMetadata{}): {"image"},
|
||||
}
|
||||
|
||||
func AllTypeNames() []string {
|
||||
|
|
26
syft/internal/testutil/chdir.go
Normal file
26
syft/internal/testutil/chdir.go
Normal file
|
@ -0,0 +1,26 @@
|
|||
package testutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Chdir(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get working directory: %v", err)
|
||||
}
|
||||
|
||||
err = os.Chdir(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to chdir to '%s': %v", dir, err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, os.Chdir(wd))
|
||||
})
|
||||
}
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
)
|
||||
|
||||
func TestIdentifyRelease(t *testing.T) {
|
||||
|
@ -336,7 +337,9 @@ func TestIdentifyRelease(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
s, err := source.NewFromDirectoryPath(test.fixture)
|
||||
s, err := directorysource.New(directorysource.Config{
|
||||
Path: test.fixture,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := s.FileResolver(source.SquashedScope)
|
||||
|
|
|
@ -21,6 +21,8 @@ import (
|
|||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
var mustUseOriginalBinaries = flag.Bool("must-use-original-binaries", false, "force the use of binaries for testing (instead of snippets)")
|
||||
|
@ -896,7 +898,7 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
|
|||
// full binaries are tested (no snippets), and if no binary is found the test will be skipped.
|
||||
path := testutil.SnippetOrBinary(t, test.logicalFixture, *mustUseOriginalBinaries)
|
||||
|
||||
src, err := source.NewFromDirectoryPath(path)
|
||||
src, err := directorysource.NewFromPath(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
|
@ -936,8 +938,9 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
|||
c := NewClassifierCataloger(DefaultClassifierCatalogerConfig())
|
||||
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
|
||||
src, err := source.NewFromStereoscopeImageObject(img, test.fixtureImage, nil)
|
||||
require.NoError(t, err)
|
||||
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: test.fixtureImage,
|
||||
})
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
@ -966,7 +969,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
|
|||
func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
|
||||
c := NewClassifierCataloger(DefaultClassifierCatalogerConfig())
|
||||
|
||||
src, err := source.NewFromDirectoryPath("test-fixtures/classifiers/negative")
|
||||
src, err := directorysource.NewFromPath("test-fixtures/classifiers/negative")
|
||||
assert.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
|
@ -1080,7 +1083,7 @@ func Test_Cataloger_CustomClassifiers(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
c := NewClassifierCataloger(test.config)
|
||||
|
||||
src, err := source.NewFromDirectoryPath(test.fixtureDir)
|
||||
src, err := directorysource.NewFromPath(test.fixtureDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := src.FileResolver(source.SquashedScope)
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
type locationComparer func(x, y file.Location) bool
|
||||
|
@ -88,7 +90,7 @@ func DefaultLicenseComparer(x, y pkg.License) bool {
|
|||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
s, err := source.NewFromDirectoryPath(path)
|
||||
s, err := directorysource.NewFromPath(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := s.FileResolver(source.AllLayersScope)
|
||||
|
@ -152,8 +154,9 @@ func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *Cat
|
|||
t.Helper()
|
||||
img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName)
|
||||
|
||||
s, err := source.NewFromStereoscopeImageObject(img, fixtureName, nil)
|
||||
require.NoError(t, err)
|
||||
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
|
||||
Reference: fixtureName,
|
||||
})
|
||||
|
||||
r, err := s.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -1,207 +0,0 @@
|
|||
package source
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
)
|
||||
|
||||
type detectedType string
|
||||
|
||||
const (
|
||||
// unknownType is the default scheme
|
||||
unknownType detectedType = "unknown-type"
|
||||
|
||||
// directoryType indicates the source being cataloged is a directory on the root filesystem
|
||||
directoryType detectedType = "directory-type"
|
||||
|
||||
// containerImageType indicates the source being cataloged is a container image
|
||||
containerImageType detectedType = "container-image-type"
|
||||
|
||||
// fileType indicates the source being cataloged is a single file
|
||||
fileType detectedType = "file-type"
|
||||
)
|
||||
|
||||
type sourceResolver func(string) (image.Source, string, error)
|
||||
|
||||
// Detection is an object that captures the detected user input regarding source location, scheme, and provider type.
|
||||
// It acts as a struct input for some source constructors.
|
||||
type Detection struct {
|
||||
detectedType detectedType
|
||||
imageSource image.Source
|
||||
location string
|
||||
}
|
||||
|
||||
func (d Detection) IsContainerImage() bool {
|
||||
return d.detectedType == containerImageType
|
||||
}
|
||||
|
||||
type DetectConfig struct {
|
||||
DefaultImageSource string
|
||||
}
|
||||
|
||||
func DefaultDetectConfig() DetectConfig {
|
||||
return DetectConfig{}
|
||||
}
|
||||
|
||||
// Detect generates a source Detection that can be used as an argument to generate a new source
|
||||
// from specific providers including a registry, with an explicit name.
|
||||
func Detect(userInput string, cfg DetectConfig) (*Detection, error) {
|
||||
fs := afero.NewOsFs()
|
||||
ty, src, location, err := detect(fs, image.DetectSource, userInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if src == image.UnknownSource {
|
||||
// only run for these two schemes
|
||||
// only check on scan command, attest we automatically try to pull from userInput
|
||||
switch ty {
|
||||
case containerImageType, unknownType:
|
||||
ty = containerImageType
|
||||
location = userInput
|
||||
if cfg.DefaultImageSource != "" {
|
||||
src = parseDefaultImageSource(cfg.DefaultImageSource)
|
||||
} else {
|
||||
src = image.DetermineDefaultImagePullSource(userInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect user input for downstream consumption
|
||||
return &Detection{
|
||||
detectedType: ty,
|
||||
imageSource: src,
|
||||
location: location,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type DetectionSourceConfig struct {
|
||||
Alias Alias
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Platform *image.Platform
|
||||
Exclude ExcludeConfig
|
||||
DigestAlgorithms []crypto.Hash
|
||||
BasePath string
|
||||
}
|
||||
|
||||
func DefaultDetectionSourceConfig() DetectionSourceConfig {
|
||||
return DetectionSourceConfig{
|
||||
DigestAlgorithms: []crypto.Hash{
|
||||
crypto.SHA256,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSource produces a Source based on userInput like dir: or image:tag
|
||||
func (d Detection) NewSource(cfg DetectionSourceConfig) (Source, error) {
|
||||
var err error
|
||||
var src Source
|
||||
|
||||
if d.detectedType != containerImageType && cfg.Platform != nil {
|
||||
return nil, fmt.Errorf("cannot specify a platform for a non-image source")
|
||||
}
|
||||
|
||||
switch d.detectedType {
|
||||
case fileType:
|
||||
src, err = NewFromFile(
|
||||
FileConfig{
|
||||
Path: d.location,
|
||||
Exclude: cfg.Exclude,
|
||||
DigestAlgorithms: cfg.DigestAlgorithms,
|
||||
Alias: cfg.Alias,
|
||||
},
|
||||
)
|
||||
case directoryType:
|
||||
base := cfg.BasePath
|
||||
if base == "" {
|
||||
base = d.location
|
||||
}
|
||||
src, err = NewFromDirectory(
|
||||
DirectoryConfig{
|
||||
Path: d.location,
|
||||
Base: base,
|
||||
Exclude: cfg.Exclude,
|
||||
Alias: cfg.Alias,
|
||||
},
|
||||
)
|
||||
case containerImageType:
|
||||
src, err = NewFromStereoscopeImage(
|
||||
StereoscopeImageConfig{
|
||||
Reference: d.location,
|
||||
From: d.imageSource,
|
||||
Platform: cfg.Platform,
|
||||
RegistryOptions: cfg.RegistryOptions,
|
||||
Exclude: cfg.Exclude,
|
||||
Alias: cfg.Alias,
|
||||
},
|
||||
)
|
||||
default:
|
||||
err = fmt.Errorf("unable to process input for scanning")
|
||||
}
|
||||
|
||||
return src, err
|
||||
}
|
||||
|
||||
func detect(fs afero.Fs, imageSourceResolver sourceResolver, userInput string) (detectedType, image.Source, string, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(userInput, "dir:"):
|
||||
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
||||
if err != nil {
|
||||
return unknownType, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
|
||||
}
|
||||
return directoryType, image.UnknownSource, dirLocation, nil
|
||||
|
||||
case strings.HasPrefix(userInput, "file:"):
|
||||
fileLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "file:"))
|
||||
if err != nil {
|
||||
return unknownType, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
|
||||
}
|
||||
return fileType, image.UnknownSource, fileLocation, nil
|
||||
}
|
||||
|
||||
// try the most specific sources first and move out towards more generic sources.
|
||||
|
||||
// first: let's try the image detector, which has more scheme parsing internal to stereoscope
|
||||
src, imageSpec, err := imageSourceResolver(userInput)
|
||||
if err == nil && src != image.UnknownSource {
|
||||
return containerImageType, src, imageSpec, nil
|
||||
}
|
||||
|
||||
// next: let's try more generic sources (dir, file, etc.)
|
||||
location, err := homedir.Expand(userInput)
|
||||
if err != nil {
|
||||
return unknownType, image.UnknownSource, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||
}
|
||||
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return unknownType, src, "", nil
|
||||
}
|
||||
|
||||
if fileMeta.IsDir() {
|
||||
return directoryType, src, location, nil
|
||||
}
|
||||
|
||||
return fileType, src, location, nil
|
||||
}
|
||||
|
||||
func parseDefaultImageSource(defaultImageSource string) image.Source {
|
||||
switch defaultImageSource {
|
||||
case "registry":
|
||||
return image.OciRegistrySource
|
||||
case "docker":
|
||||
return image.DockerDaemonSource
|
||||
case "podman":
|
||||
return image.PodmanDaemonSource
|
||||
case "containerd":
|
||||
return image.ContainerdDaemonSource
|
||||
default:
|
||||
return image.UnknownSource
|
||||
}
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
package source
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
)
|
||||
|
||||
func Test_Detect(t *testing.T) {
|
||||
type detectorResult struct {
|
||||
src image.Source
|
||||
ref string
|
||||
err error
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
userInput string
|
||||
dirs []string
|
||||
files []string
|
||||
detection detectorResult
|
||||
expectedScheme detectedType
|
||||
expectedLocation string
|
||||
}{
|
||||
{
|
||||
name: "docker-image-ref",
|
||||
userInput: "wagoodman/dive:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive:latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "wagoodman/dive:latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-ref-no-tag",
|
||||
userInput: "wagoodman/dive",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "wagoodman/dive",
|
||||
},
|
||||
{
|
||||
name: "registry-image-explicit-scheme",
|
||||
userInput: "registry:wagoodman/dive:latest",
|
||||
detection: detectorResult{
|
||||
src: image.OciRegistrySource,
|
||||
ref: "wagoodman/dive:latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "wagoodman/dive:latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-explicit-scheme",
|
||||
userInput: "docker:wagoodman/dive:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive:latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "wagoodman/dive:latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-explicit-scheme-no-tag",
|
||||
userInput: "docker:wagoodman/dive",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "wagoodman/dive",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "wagoodman/dive",
|
||||
},
|
||||
{
|
||||
name: "docker-image-edge-case",
|
||||
userInput: "docker:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
// we expected to be able to handle this case better, however, I don't see a way to do this
|
||||
// the user will need to provide more explicit input (docker:docker:latest)
|
||||
expectedLocation: "latest",
|
||||
},
|
||||
{
|
||||
name: "docker-image-edge-case-explicit",
|
||||
userInput: "docker:docker:latest",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "docker:latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
// we expected to be able to handle this case better, however, I don't see a way to do this
|
||||
// the user will need to provide more explicit input (docker:docker:latest)
|
||||
expectedLocation: "docker:latest",
|
||||
},
|
||||
{
|
||||
name: "oci-tar",
|
||||
userInput: "some/path-to-file",
|
||||
detection: detectorResult{
|
||||
src: image.OciTarballSource,
|
||||
ref: "some/path-to-file",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "some/path-to-file",
|
||||
},
|
||||
{
|
||||
name: "oci-dir",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.OciDirectorySource,
|
||||
ref: "some/path-to-dir",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "guess-dir",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "generic-dir-does-not-exist",
|
||||
userInput: "some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.DockerDaemonSource,
|
||||
ref: "some/path-to-dir",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "found-podman-image-scheme",
|
||||
userInput: "podman:something:latest",
|
||||
detection: detectorResult{
|
||||
src: image.PodmanDaemonSource,
|
||||
ref: "something:latest",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "something:latest",
|
||||
},
|
||||
{
|
||||
name: "explicit-dir",
|
||||
userInput: "dir:some/path-to-dir",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"some/path-to-dir"},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: "some/path-to-dir",
|
||||
},
|
||||
{
|
||||
name: "explicit-file",
|
||||
userInput: "file:some/path-to-file",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
files: []string{"some/path-to-file"},
|
||||
expectedScheme: fileType,
|
||||
expectedLocation: "some/path-to-file",
|
||||
},
|
||||
{
|
||||
name: "implicit-file",
|
||||
userInput: "some/path-to-file",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
files: []string{"some/path-to-file"},
|
||||
expectedScheme: fileType,
|
||||
expectedLocation: "some/path-to-file",
|
||||
},
|
||||
{
|
||||
name: "explicit-current-dir",
|
||||
userInput: "dir:.",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: ".",
|
||||
},
|
||||
{
|
||||
name: "current-dir",
|
||||
userInput: ".",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: ".",
|
||||
},
|
||||
// we should support tilde expansion
|
||||
{
|
||||
name: "tilde-expansion-image-implicit",
|
||||
userInput: "~/some-path",
|
||||
detection: detectorResult{
|
||||
src: image.OciDirectorySource,
|
||||
ref: "~/some-path",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-implicit",
|
||||
userInput: "~/some-path",
|
||||
detection: detectorResult{
|
||||
src: image.UnknownSource,
|
||||
ref: "",
|
||||
},
|
||||
dirs: []string{"~/some-path"},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-explicit-exists",
|
||||
userInput: "dir:~/some-path",
|
||||
dirs: []string{"~/some-path"},
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-explicit-dne",
|
||||
userInput: "dir:~/some-path",
|
||||
expectedScheme: directoryType,
|
||||
expectedLocation: "~/some-path",
|
||||
},
|
||||
{
|
||||
name: "tilde-expansion-dir-implicit-dne",
|
||||
userInput: "~/some-path",
|
||||
expectedScheme: unknownType,
|
||||
expectedLocation: "",
|
||||
},
|
||||
{
|
||||
name: "podman-image",
|
||||
userInput: "containerd:anchore/syft",
|
||||
detection: detectorResult{
|
||||
src: image.PodmanDaemonSource,
|
||||
ref: "anchore/syft",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "anchore/syft",
|
||||
},
|
||||
{
|
||||
name: "containerd-image",
|
||||
userInput: "containerd:anchore/syft",
|
||||
detection: detectorResult{
|
||||
src: image.ContainerdDaemonSource,
|
||||
ref: "anchore/syft",
|
||||
},
|
||||
expectedScheme: containerImageType,
|
||||
expectedLocation: "anchore/syft",
|
||||
},
|
||||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
for _, p := range test.dirs {
|
||||
expandedExpectedLocation, err := homedir.Expand(p)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", p, err)
|
||||
}
|
||||
err = fs.Mkdir(expandedExpectedLocation, os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create dummy dir: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range test.files {
|
||||
expandedExpectedLocation, err := homedir.Expand(p)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", p, err)
|
||||
}
|
||||
_, err = fs.Create(expandedExpectedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create dummy file: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
imageDetector := func(string) (image.Source, string, error) {
|
||||
// lean on the users real home directory value
|
||||
switch test.detection.src {
|
||||
case image.OciDirectorySource, image.DockerTarballSource, image.OciTarballSource:
|
||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||
}
|
||||
return test.detection.src, expandedExpectedLocation, test.detection.err
|
||||
default:
|
||||
return test.detection.src, test.detection.ref, test.detection.err
|
||||
}
|
||||
}
|
||||
|
||||
actualScheme, actualSource, actualLocation, err := detect(fs, imageDetector, test.userInput)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err : %+v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.detection.src, actualSource, "mismatched source")
|
||||
assert.Equal(t, test.expectedScheme, actualScheme, "mismatched scheme")
|
||||
|
||||
// lean on the users real home directory value
|
||||
expandedExpectedLocation, err := homedir.Expand(test.expectedLocation)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to expand path=%q: %+v", test.expectedLocation, err)
|
||||
}
|
||||
|
||||
assert.Equal(t, expandedExpectedLocation, actualLocation, "mismatched location")
|
||||
})
|
||||
}
|
||||
}
|
6
syft/source/directory_metadata.go
Normal file
6
syft/source/directory_metadata.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package source
|
||||
|
||||
type DirectoryMetadata struct {
|
||||
Path string `json:"path" yaml:"path"`
|
||||
Base string `json:"-" yaml:"-"` // though this is important, for display purposes it leaks too much information (abs paths)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package source
|
||||
package directorysource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -14,37 +14,34 @@ import (
|
|||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/internal"
|
||||
)
|
||||
|
||||
var _ Source = (*DirectorySource)(nil)
|
||||
var _ source.Source = (*directorySource)(nil)
|
||||
|
||||
type DirectoryConfig struct {
|
||||
type Config struct {
|
||||
Path string
|
||||
Base string
|
||||
Exclude ExcludeConfig
|
||||
Alias Alias
|
||||
Exclude source.ExcludeConfig
|
||||
Alias source.Alias
|
||||
}
|
||||
|
||||
type DirectorySourceMetadata struct {
|
||||
Path string `json:"path" yaml:"path"`
|
||||
Base string `json:"-" yaml:"-"` // though this is important, for display purposes it leaks too much information (abs paths)
|
||||
}
|
||||
|
||||
type DirectorySource struct {
|
||||
type directorySource struct {
|
||||
id artifact.ID
|
||||
config DirectoryConfig
|
||||
config Config
|
||||
resolver *fileresolver.Directory
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewFromDirectoryPath(path string) (*DirectorySource, error) {
|
||||
cfg := DirectoryConfig{
|
||||
func NewFromPath(path string) (source.Source, error) {
|
||||
cfg := Config{
|
||||
Path: path,
|
||||
}
|
||||
return NewFromDirectory(cfg)
|
||||
return New(cfg)
|
||||
}
|
||||
|
||||
func NewFromDirectory(cfg DirectoryConfig) (*DirectorySource, error) {
|
||||
func New(cfg Config) (source.Source, error) {
|
||||
fi, err := os.Stat(cfg.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to stat path=%q: %w", cfg.Path, err)
|
||||
|
@ -54,7 +51,7 @@ func NewFromDirectory(cfg DirectoryConfig) (*DirectorySource, error) {
|
|||
return nil, fmt.Errorf("given path is not a directory: %q", cfg.Path)
|
||||
}
|
||||
|
||||
return &DirectorySource{
|
||||
return &directorySource{
|
||||
id: deriveIDFromDirectory(cfg),
|
||||
config: cfg,
|
||||
mutex: &sync.Mutex{},
|
||||
|
@ -66,7 +63,7 @@ func NewFromDirectory(cfg DirectoryConfig) (*DirectorySource, error) {
|
|||
// from the path provided with an attempt to prune a prefix if a base is given. Since the contents of the directory
|
||||
// are not considered, there is no semantic meaning to the artifact ID -- this is why the alias is preferred without
|
||||
// consideration for the path.
|
||||
func deriveIDFromDirectory(cfg DirectoryConfig) artifact.ID {
|
||||
func deriveIDFromDirectory(cfg Config) artifact.ID {
|
||||
var info string
|
||||
if !cfg.Alias.IsEmpty() {
|
||||
// don't use any of the path information -- instead use the alias name and version as the artifact ID.
|
||||
|
@ -78,7 +75,7 @@ func deriveIDFromDirectory(cfg DirectoryConfig) artifact.ID {
|
|||
info = cleanDirPath(cfg.Path, cfg.Base)
|
||||
}
|
||||
|
||||
return artifactIDFromDigest(digest.SHA256.FromString(filepath.Clean(info)).String())
|
||||
return internal.ArtifactIDFromDigest(digest.SHA256.FromString(filepath.Clean(info)).String())
|
||||
}
|
||||
|
||||
func cleanDirPath(path, base string) string {
|
||||
|
@ -108,11 +105,11 @@ func cleanDirPath(path, base string) string {
|
|||
return path
|
||||
}
|
||||
|
||||
func (s DirectorySource) ID() artifact.ID {
|
||||
func (s directorySource) ID() artifact.ID {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s DirectorySource) Describe() Description {
|
||||
func (s directorySource) Describe() source.Description {
|
||||
name := cleanDirPath(s.config.Path, s.config.Base)
|
||||
version := ""
|
||||
if !s.config.Alias.IsEmpty() {
|
||||
|
@ -124,23 +121,23 @@ func (s DirectorySource) Describe() Description {
|
|||
version = a.Version
|
||||
}
|
||||
}
|
||||
return Description{
|
||||
return source.Description{
|
||||
ID: string(s.id),
|
||||
Name: name,
|
||||
Version: version,
|
||||
Metadata: DirectorySourceMetadata{
|
||||
Metadata: source.DirectoryMetadata{
|
||||
Path: s.config.Path,
|
||||
Base: s.config.Base,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DirectorySource) FileResolver(_ Scope) (file.Resolver, error) {
|
||||
func (s *directorySource) FileResolver(_ source.Scope) (file.Resolver, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.resolver == nil {
|
||||
exclusionFunctions, err := getDirectoryExclusionFunctions(s.config.Path, s.config.Exclude.Paths)
|
||||
exclusionFunctions, err := GetDirectoryExclusionFunctions(s.config.Path, s.config.Exclude.Paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -156,14 +153,14 @@ func (s *DirectorySource) FileResolver(_ Scope) (file.Resolver, error) {
|
|||
return s.resolver, nil
|
||||
}
|
||||
|
||||
func (s *DirectorySource) Close() error {
|
||||
func (s *directorySource) Close() error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.resolver = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDirectoryExclusionFunctions(root string, exclusions []string) ([]fileresolver.PathIndexVisitor, error) {
|
||||
func GetDirectoryExclusionFunctions(root string, exclusions []string) ([]fileresolver.PathIndexVisitor, error) {
|
||||
if len(exclusions) == 0 {
|
||||
return nil, nil
|
||||
}
|
65
syft/source/directorysource/directory_source_provider.go
Normal file
65
syft/source/directorysource/directory_source_provider.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package directorysource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func NewSourceProvider(path string, exclude source.ExcludeConfig, alias source.Alias, basePath string) source.Provider {
|
||||
return &directorySourceProvider{
|
||||
path: path,
|
||||
basePath: basePath,
|
||||
exclude: exclude,
|
||||
alias: alias,
|
||||
}
|
||||
}
|
||||
|
||||
type directorySourceProvider struct {
|
||||
path string
|
||||
basePath string
|
||||
exclude source.ExcludeConfig
|
||||
alias source.Alias
|
||||
}
|
||||
|
||||
func (l directorySourceProvider) Name() string {
|
||||
return "local-directory"
|
||||
}
|
||||
|
||||
func (l directorySourceProvider) Provide(_ context.Context) (source.Source, error) {
|
||||
location, err := homedir.Expand(l.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||
}
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to stat location: %w", err)
|
||||
}
|
||||
|
||||
if !fileMeta.IsDir() {
|
||||
return nil, fmt.Errorf("not a directory source: %s", l.path)
|
||||
}
|
||||
|
||||
return New(
|
||||
Config{
|
||||
Path: location,
|
||||
Base: basePath(l.basePath, location),
|
||||
Exclude: l.exclude,
|
||||
Alias: l.alias,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// FIXME why is the base always being set instead of left as empty string?
|
||||
func basePath(base, location string) string {
|
||||
if base == "" {
|
||||
base = location
|
||||
}
|
||||
return base
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package source
|
||||
package directorysource
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
@ -13,9 +13,13 @@ import (
|
|||
"github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/internal/testutil"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestNewFromDirectory(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -54,7 +58,7 @@ func TestNewFromDirectory(t *testing.T) {
|
|||
if test.cxErr == nil {
|
||||
test.cxErr = require.NoError
|
||||
}
|
||||
src, err := NewFromDirectory(DirectoryConfig{
|
||||
src, err := New(Config{
|
||||
Path: test.input,
|
||||
})
|
||||
test.cxErr(t, err)
|
||||
|
@ -65,9 +69,9 @@ func TestNewFromDirectory(t *testing.T) {
|
|||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
assert.Equal(t, test.input, src.Describe().Metadata.(DirectorySourceMetadata).Path)
|
||||
assert.Equal(t, test.input, src.Describe().Metadata.(source.DirectoryMetadata).Path)
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
refs, err := res.FilesByPath(test.inputPaths...)
|
||||
|
@ -82,6 +86,8 @@ func TestNewFromDirectory(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_DirectorySource_FilesByGlob(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -109,10 +115,10 @@ func Test_DirectorySource_FilesByGlob(t *testing.T) {
|
|||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, err := NewFromDirectory(DirectoryConfig{Path: test.input})
|
||||
src, err := New(Config{Path: test.input})
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
|
@ -129,6 +135,8 @@ func Test_DirectorySource_FilesByGlob(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_DirectorySource_Exclusions(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -270,9 +278,9 @@ func Test_DirectorySource_Exclusions(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, err := NewFromDirectory(DirectoryConfig{
|
||||
src, err := New(Config{
|
||||
Path: test.input,
|
||||
Exclude: ExcludeConfig{
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: test.exclusions,
|
||||
},
|
||||
})
|
||||
|
@ -282,13 +290,13 @@ func Test_DirectorySource_Exclusions(t *testing.T) {
|
|||
})
|
||||
|
||||
if test.err {
|
||||
_, err = src.FileResolver(SquashedScope)
|
||||
_, err = src.FileResolver(source.SquashedScope)
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
locations, err := res.FilesByGlob(test.glob)
|
||||
|
@ -388,7 +396,7 @@ func Test_getDirectoryExclusionFunctions_crossPlatform(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
fns, err := getDirectoryExclusionFunctions(test.root, []string{test.exclude})
|
||||
fns, err := GetDirectoryExclusionFunctions(test.root, []string{test.exclude})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, f := range fns {
|
||||
|
@ -400,6 +408,8 @@ func Test_getDirectoryExclusionFunctions_crossPlatform(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_DirectorySource_FilesByPathDoesNotExist(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -414,13 +424,13 @@ func Test_DirectorySource_FilesByPathDoesNotExist(t *testing.T) {
|
|||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, err := NewFromDirectory(DirectoryConfig{Path: test.input})
|
||||
src, err := New(Config{Path: test.input})
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
refs, err := res.FilesByPath(test.path)
|
||||
|
@ -432,41 +442,43 @@ func Test_DirectorySource_FilesByPathDoesNotExist(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_DirectorySource_ID(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg DirectoryConfig
|
||||
cfg Config
|
||||
want artifact.ID
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
cfg: DirectoryConfig{},
|
||||
cfg: Config{},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "to a non-existent directory",
|
||||
cfg: DirectoryConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/does-not-exist",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "with odd unclean path through non-existent directory",
|
||||
cfg: DirectoryConfig{Path: "test-fixtures/does-not-exist/../"},
|
||||
cfg: Config{Path: "test-fixtures/does-not-exist/../"},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "to a file (not a directory)",
|
||||
cfg: DirectoryConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/image-simple/Dockerfile",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "to dir with name and version",
|
||||
cfg: DirectoryConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures",
|
||||
Alias: Alias{
|
||||
Alias: source.Alias{
|
||||
Name: "name-me-that!",
|
||||
Version: "version-me-this!",
|
||||
},
|
||||
|
@ -475,9 +487,9 @@ func Test_DirectorySource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "to different dir with name and version",
|
||||
cfg: DirectoryConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/image-simple",
|
||||
Alias: Alias{
|
||||
Alias: source.Alias{
|
||||
Name: "name-me-that!",
|
||||
Version: "version-me-this!",
|
||||
},
|
||||
|
@ -487,20 +499,20 @@ func Test_DirectorySource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "with path",
|
||||
cfg: DirectoryConfig{Path: "./test-fixtures"},
|
||||
cfg: Config{Path: "./test-fixtures"},
|
||||
want: artifact.ID("c2f936b0054dc6114fc02a3446bf8916bde8fdf87166a23aee22ea011b443522"),
|
||||
},
|
||||
{
|
||||
name: "with unclean path",
|
||||
cfg: DirectoryConfig{Path: "test-fixtures/image-simple/../"},
|
||||
cfg: Config{Path: "test-fixtures/image-simple/../"},
|
||||
want: artifact.ID("c2f936b0054dc6114fc02a3446bf8916bde8fdf87166a23aee22ea011b443522"),
|
||||
},
|
||||
{
|
||||
name: "other fields do not affect ID",
|
||||
cfg: DirectoryConfig{
|
||||
cfg: Config{
|
||||
Path: "test-fixtures",
|
||||
Base: "a-base!",
|
||||
Exclude: ExcludeConfig{
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
|
@ -512,7 +524,7 @@ func Test_DirectorySource_ID(t *testing.T) {
|
|||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
s, err := NewFromDirectory(tt.cfg)
|
||||
s, err := New(tt.cfg)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -523,6 +535,7 @@ func Test_DirectorySource_ID(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_cleanDirPath(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
abs, err := filepath.Abs("test-fixtures")
|
||||
require.NoError(t, err)
|
|
@ -12,7 +12,7 @@
|
|||
// - https://github.com/golang/go/blob/3aea422e2cb8b1ec2e0c2774be97fe96c7299838/src/path/filepath/path_windows.go#L216
|
||||
// ... which means we can't extract this functionality without build tags.
|
||||
|
||||
package source
|
||||
package directorysource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
@ -53,7 +53,7 @@ func Test_DirectorySource_crossPlatformExclusions(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
fns, err := getDirectoryExclusionFunctions(test.root, []string{test.exclude})
|
||||
fns, err := GetDirectoryExclusionFunctions(test.root, []string{test.exclude})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, f := range fns {
|
9
syft/source/file_metadata.go
Normal file
9
syft/source/file_metadata.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package source
|
||||
|
||||
import "github.com/anchore/syft/syft/file"
|
||||
|
||||
type FileMetadata struct {
|
||||
Path string `json:"path" yaml:"path"`
|
||||
Digests []file.Digest `json:"digests,omitempty" yaml:"digests,omitempty"`
|
||||
MIMEType string `json:"mimeType" yaml:"mimeType"`
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package source
|
||||
package filesource
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
@ -18,27 +18,24 @@ import (
|
|||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/internal"
|
||||
)
|
||||
|
||||
var _ Source = (*FileSource)(nil)
|
||||
var _ source.Source = (*fileSource)(nil)
|
||||
|
||||
type FileConfig struct {
|
||||
type Config struct {
|
||||
Path string
|
||||
Exclude ExcludeConfig
|
||||
Exclude source.ExcludeConfig
|
||||
DigestAlgorithms []crypto.Hash
|
||||
Alias Alias
|
||||
Alias source.Alias
|
||||
}
|
||||
|
||||
type FileSourceMetadata struct {
|
||||
Path string `json:"path" yaml:"path"`
|
||||
Digests []file.Digest `json:"digests,omitempty" yaml:"digests,omitempty"`
|
||||
MIMEType string `json:"mimeType" yaml:"mimeType"`
|
||||
}
|
||||
|
||||
type FileSource struct {
|
||||
type fileSource struct {
|
||||
id artifact.ID
|
||||
digestForVersion string
|
||||
config FileConfig
|
||||
config Config
|
||||
resolver *fileresolver.Directory
|
||||
mutex *sync.Mutex
|
||||
closer func() error
|
||||
|
@ -47,7 +44,11 @@ type FileSource struct {
|
|||
analysisPath string
|
||||
}
|
||||
|
||||
func NewFromFile(cfg FileConfig) (*FileSource, error) {
|
||||
func NewFromPath(path string) (source.Source, error) {
|
||||
return New(Config{Path: path})
|
||||
}
|
||||
|
||||
func New(cfg Config) (source.Source, error) {
|
||||
fileMeta, err := os.Stat(cfg.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to stat path=%q: %w", cfg.Path, err)
|
||||
|
@ -83,7 +84,7 @@ func NewFromFile(cfg FileConfig) (*FileSource, error) {
|
|||
|
||||
id, versionDigest := deriveIDFromFile(cfg)
|
||||
|
||||
return &FileSource{
|
||||
return &fileSource{
|
||||
id: id,
|
||||
config: cfg,
|
||||
mutex: &sync.Mutex{},
|
||||
|
@ -98,7 +99,7 @@ func NewFromFile(cfg FileConfig) (*FileSource, error) {
|
|||
// deriveIDFromFile derives an artifact ID from the contents of a file. If an alias is provided, it will be included
|
||||
// in the ID derivation (along with contents). This way if the user scans the same item but is considered to be
|
||||
// logically different, then ID will express that.
|
||||
func deriveIDFromFile(cfg FileConfig) (artifact.ID, string) {
|
||||
func deriveIDFromFile(cfg Config) (artifact.ID, string) {
|
||||
d := digestOfFileContents(cfg.Path)
|
||||
info := d
|
||||
|
||||
|
@ -108,14 +109,14 @@ func deriveIDFromFile(cfg FileConfig) (artifact.ID, string) {
|
|||
info += fmt.Sprintf(":%s@%s", cfg.Alias.Name, cfg.Alias.Version)
|
||||
}
|
||||
|
||||
return artifactIDFromDigest(digest.SHA256.FromString(info).String()), d
|
||||
return internal.ArtifactIDFromDigest(digest.SHA256.FromString(info).String()), d
|
||||
}
|
||||
|
||||
func (s FileSource) ID() artifact.ID {
|
||||
func (s fileSource) ID() artifact.ID {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s FileSource) Describe() Description {
|
||||
func (s fileSource) Describe() source.Description {
|
||||
name := path.Base(s.config.Path)
|
||||
version := s.digestForVersion
|
||||
if !s.config.Alias.IsEmpty() {
|
||||
|
@ -128,11 +129,11 @@ func (s FileSource) Describe() Description {
|
|||
version = a.Version
|
||||
}
|
||||
}
|
||||
return Description{
|
||||
return source.Description{
|
||||
ID: string(s.id),
|
||||
Name: name,
|
||||
Version: version,
|
||||
Metadata: FileSourceMetadata{
|
||||
Metadata: source.FileMetadata{
|
||||
Path: s.config.Path,
|
||||
Digests: s.digests,
|
||||
MIMEType: s.mimeType,
|
||||
|
@ -140,7 +141,7 @@ func (s FileSource) Describe() Description {
|
|||
}
|
||||
}
|
||||
|
||||
func (s FileSource) FileResolver(_ Scope) (file.Resolver, error) {
|
||||
func (s fileSource) FileResolver(_ source.Scope) (file.Resolver, error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
|
@ -148,7 +149,7 @@ func (s FileSource) FileResolver(_ Scope) (file.Resolver, error) {
|
|||
return s.resolver, nil
|
||||
}
|
||||
|
||||
exclusionFunctions, err := getDirectoryExclusionFunctions(s.analysisPath, s.config.Exclude.Paths)
|
||||
exclusionFunctions, err := directorysource.GetDirectoryExclusionFunctions(s.analysisPath, s.config.Exclude.Paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -221,7 +222,7 @@ func absoluteSymlinkFreePathToParent(path string) (string, error) {
|
|||
return filepath.Dir(dereferencedAbsAnalysisPath), nil
|
||||
}
|
||||
|
||||
func (s *FileSource) Close() error {
|
||||
func (s *fileSource) Close() error {
|
||||
if s.closer == nil {
|
||||
return nil
|
||||
}
|
58
syft/source/filesource/file_source_provider.go
Normal file
58
syft/source/filesource/file_source_provider.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package filesource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func NewSourceProvider(path string, exclude source.ExcludeConfig, digestAlgorithms []crypto.Hash, alias source.Alias) source.Provider {
|
||||
return &fileSourceProvider{
|
||||
path: path,
|
||||
exclude: exclude,
|
||||
digestAlgorithms: digestAlgorithms,
|
||||
alias: alias,
|
||||
}
|
||||
}
|
||||
|
||||
type fileSourceProvider struct {
|
||||
path string
|
||||
exclude source.ExcludeConfig
|
||||
digestAlgorithms []crypto.Hash
|
||||
alias source.Alias
|
||||
}
|
||||
|
||||
func (p fileSourceProvider) Name() string {
|
||||
return "local-file"
|
||||
}
|
||||
|
||||
func (p fileSourceProvider) Provide(_ context.Context) (source.Source, error) {
|
||||
location, err := homedir.Expand(p.path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||
}
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
fileMeta, err := fs.Stat(location)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to stat location: %w", err)
|
||||
}
|
||||
|
||||
if fileMeta.IsDir() {
|
||||
return nil, fmt.Errorf("not a file source: %s", p.path)
|
||||
}
|
||||
|
||||
return New(
|
||||
Config{
|
||||
Path: location,
|
||||
Exclude: p.exclude,
|
||||
DigestAlgorithms: p.digestAlgorithms,
|
||||
Alias: p.alias,
|
||||
},
|
||||
)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package source
|
||||
package filesource
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
@ -14,9 +14,13 @@ import (
|
|||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/testutil"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestNewFromFile(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -67,7 +71,7 @@ func TestNewFromFile(t *testing.T) {
|
|||
}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, err := NewFromFile(FileConfig{
|
||||
src, err := New(Config{
|
||||
Path: test.input,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -75,9 +79,9 @@ func TestNewFromFile(t *testing.T) {
|
|||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
assert.Equal(t, test.input, src.Describe().Metadata.(FileSourceMetadata).Path)
|
||||
assert.Equal(t, test.input, src.Describe().Metadata.(source.FileMetadata).Path)
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
refs, err := test.testPathFn(res)
|
||||
|
@ -92,6 +96,8 @@ func TestNewFromFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewFromFile_WithArchive(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -120,7 +126,7 @@ func TestNewFromFile_WithArchive(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
archivePath := setupArchiveTest(t, test.input, test.layer2)
|
||||
|
||||
src, err := NewFromFile(FileConfig{
|
||||
src, err := New(Config{
|
||||
Path: archivePath,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -128,9 +134,9 @@ func TestNewFromFile_WithArchive(t *testing.T) {
|
|||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
assert.Equal(t, archivePath, src.Describe().Metadata.(FileSourceMetadata).Path)
|
||||
assert.Equal(t, archivePath, src.Describe().Metadata.(source.FileMetadata).Path)
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
refs, err := res.FilesByPath(test.inputPaths...)
|
||||
|
@ -226,43 +232,45 @@ func createArchive(t testing.TB, sourceDirPath, destinationArchivePath string, l
|
|||
}
|
||||
|
||||
func Test_FileSource_ID(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg FileConfig
|
||||
cfg Config
|
||||
want artifact.ID
|
||||
wantDigest string
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
cfg: FileConfig{},
|
||||
cfg: Config{},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "does not exist",
|
||||
cfg: FileConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/does-not-exist",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "to dir",
|
||||
cfg: FileConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/image-simple",
|
||||
},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
{
|
||||
name: "with path",
|
||||
cfg: FileConfig{Path: "./test-fixtures/image-simple/Dockerfile"},
|
||||
cfg: Config{Path: "./test-fixtures/image-simple/Dockerfile"},
|
||||
want: artifact.ID("db7146472cf6d49b3ac01b42812fb60020b0b4898b97491b21bb690c808d5159"),
|
||||
wantDigest: "sha256:38601c0bb4269a10ce1d00590ea7689c1117dd9274c758653934ab4f2016f80f",
|
||||
},
|
||||
{
|
||||
name: "with path and alias",
|
||||
cfg: FileConfig{
|
||||
cfg: Config{
|
||||
Path: "./test-fixtures/image-simple/Dockerfile",
|
||||
Alias: Alias{
|
||||
Alias: source.Alias{
|
||||
Name: "name-me-that!",
|
||||
Version: "version-me-this!",
|
||||
},
|
||||
|
@ -272,9 +280,9 @@ func Test_FileSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "other fields do not affect ID",
|
||||
cfg: FileConfig{
|
||||
cfg: Config{
|
||||
Path: "test-fixtures/image-simple/Dockerfile",
|
||||
Exclude: ExcludeConfig{
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: []string{"a", "b"},
|
||||
},
|
||||
},
|
||||
|
@ -287,11 +295,12 @@ func Test_FileSource_ID(t *testing.T) {
|
|||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
s, err := NewFromFile(tt.cfg)
|
||||
newSource, err := New(tt.cfg)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s := newSource.(*fileSource)
|
||||
assert.Equalf(t, tt.want, s.ID(), "ID() mismatch")
|
||||
assert.Equalf(t, tt.wantDigest, s.digestForVersion, "digestForVersion mismatch")
|
||||
})
|
27
syft/source/image_metadata.go
Normal file
27
syft/source/image_metadata.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package source
|
||||
|
||||
// ImageMetadata represents all static metadata that defines what a container image is. This is useful to later describe
|
||||
// "what" was cataloged without needing the more complicated stereoscope Image objects or FileResolver objects.
|
||||
type ImageMetadata struct {
|
||||
UserInput string `json:"userInput"`
|
||||
ID string `json:"imageID"`
|
||||
ManifestDigest string `json:"manifestDigest"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Tags []string `json:"tags"`
|
||||
Size int64 `json:"imageSize"`
|
||||
Layers []LayerMetadata `json:"layers"`
|
||||
RawManifest []byte `json:"manifest"`
|
||||
RawConfig []byte `json:"config"`
|
||||
RepoDigests []string `json:"repoDigests"`
|
||||
Architecture string `json:"architecture"`
|
||||
Variant string `json:"architectureVariant,omitempty"`
|
||||
OS string `json:"os"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// LayerMetadata represents all static metadata that defines what a container image layer is.
|
||||
type LayerMetadata struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package source
|
||||
package internal
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
@ -6,6 +6,6 @@ import (
|
|||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
|
||||
func artifactIDFromDigest(input string) artifact.ID {
|
||||
func ArtifactIDFromDigest(input string) artifact.ID {
|
||||
return artifact.ID(strings.TrimPrefix(input, "sha256:"))
|
||||
}
|
11
syft/source/provider.go
Normal file
11
syft/source/provider.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Provider is able to resolve a source request
|
||||
type Provider interface {
|
||||
Name() string
|
||||
Provide(ctx context.Context) (Source, error)
|
||||
}
|
56
syft/source/sourceproviders/source_provider_config.go
Normal file
56
syft/source/sourceproviders/source_provider_config.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package sourceproviders
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// Config is the uber-configuration for all Syft source providers
|
||||
type Config struct {
|
||||
Platform *image.Platform
|
||||
Alias source.Alias
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Exclude source.ExcludeConfig
|
||||
DigestAlgorithms []crypto.Hash
|
||||
BasePath string
|
||||
}
|
||||
|
||||
func (c *Config) WithAlias(alias source.Alias) *Config {
|
||||
c.Alias = alias
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithRegistryOptions(registryOptions *image.RegistryOptions) *Config {
|
||||
c.RegistryOptions = registryOptions
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithPlatform(platform *image.Platform) *Config {
|
||||
c.Platform = platform
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithExcludeConfig(excludeConfig source.ExcludeConfig) *Config {
|
||||
c.Exclude = excludeConfig
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithDigestAlgorithms(algorithms ...crypto.Hash) *Config {
|
||||
c.DigestAlgorithms = algorithms
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithBasePath(basePath string) *Config {
|
||||
c.BasePath = basePath
|
||||
return c
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
DigestAlgorithms: []crypto.Hash{
|
||||
crypto.SHA256,
|
||||
},
|
||||
}
|
||||
}
|
55
syft/source/sourceproviders/source_providers.go
Normal file
55
syft/source/sourceproviders/source_providers.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package sourceproviders
|
||||
|
||||
import (
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/directorysource"
|
||||
"github.com/anchore/syft/syft/source/filesource"
|
||||
"github.com/anchore/syft/syft/source/stereoscopesource"
|
||||
)
|
||||
|
||||
const (
|
||||
FileTag = stereoscope.FileTag
|
||||
DirTag = stereoscope.DirTag
|
||||
PullTag = stereoscope.PullTag
|
||||
)
|
||||
|
||||
// All returns all the configured source providers known to syft
|
||||
func All(userInput string, cfg *Config) []collections.TaggedValue[source.Provider] {
|
||||
if cfg == nil {
|
||||
cfg = DefaultConfig()
|
||||
}
|
||||
stereoscopeProviders := stereoscopeSourceProviders(userInput, cfg)
|
||||
|
||||
return collections.TaggedValueSet[source.Provider]{}.
|
||||
// --from file, dir, oci-archive, etc.
|
||||
Join(stereoscopeProviders.Select(FileTag, DirTag)...).
|
||||
Join(tagProvider(filesource.NewSourceProvider(userInput, cfg.Exclude, cfg.DigestAlgorithms, cfg.Alias), FileTag)).
|
||||
Join(tagProvider(directorysource.NewSourceProvider(userInput, cfg.Exclude, cfg.Alias, cfg.BasePath), DirTag)).
|
||||
|
||||
// --from docker, registry, etc.
|
||||
Join(stereoscopeProviders.Select(PullTag)...)
|
||||
}
|
||||
|
||||
func stereoscopeSourceProviders(userInput string, cfg *Config) collections.TaggedValueSet[source.Provider] {
|
||||
var registry image.RegistryOptions
|
||||
if cfg.RegistryOptions != nil {
|
||||
registry = *cfg.RegistryOptions
|
||||
}
|
||||
stereoscopeProviders := stereoscopesource.Providers(stereoscopesource.ProviderConfig{
|
||||
StereoscopeImageProviderConfig: stereoscope.ImageProviderConfig{
|
||||
UserInput: userInput,
|
||||
Platform: cfg.Platform,
|
||||
Registry: registry,
|
||||
},
|
||||
Alias: cfg.Alias,
|
||||
Exclude: cfg.Exclude,
|
||||
})
|
||||
return stereoscopeProviders
|
||||
}
|
||||
|
||||
func tagProvider(provider source.Provider, tags ...string) collections.TaggedValue[source.Provider] {
|
||||
return collections.NewTaggedValue(provider, append([]string{provider.Name()}, tags...)...)
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package source
|
||||
|
||||
import "github.com/anchore/stereoscope/pkg/image"
|
||||
|
||||
// StereoscopeImageSourceMetadata represents all static metadata that defines what a container image is. This is useful to later describe
|
||||
// "what" was cataloged without needing the more complicated stereoscope Image objects or FileResolver objects.
|
||||
type StereoscopeImageSourceMetadata struct {
|
||||
UserInput string `json:"userInput"`
|
||||
ID string `json:"imageID"`
|
||||
ManifestDigest string `json:"manifestDigest"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Tags []string `json:"tags"`
|
||||
Size int64 `json:"imageSize"`
|
||||
Layers []StereoscopeLayerMetadata `json:"layers"`
|
||||
RawManifest []byte `json:"manifest"`
|
||||
RawConfig []byte `json:"config"`
|
||||
RepoDigests []string `json:"repoDigests"`
|
||||
Architecture string `json:"architecture"`
|
||||
Variant string `json:"architectureVariant,omitempty"`
|
||||
OS string `json:"os"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// StereoscopeLayerMetadata represents all static metadata that defines what a container image layer is.
|
||||
type StereoscopeLayerMetadata struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Digest string `json:"digest"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// NewStereoscopeImageMetadata creates a new ImageMetadata object populated from the given stereoscope Image object and user configuration.
|
||||
func NewStereoscopeImageMetadata(img *image.Image, userInput string) StereoscopeImageSourceMetadata {
|
||||
// populate artifacts...
|
||||
tags := make([]string, len(img.Metadata.Tags))
|
||||
for idx, tag := range img.Metadata.Tags {
|
||||
tags[idx] = tag.String()
|
||||
}
|
||||
theImg := StereoscopeImageSourceMetadata{
|
||||
ID: img.Metadata.ID,
|
||||
UserInput: userInput,
|
||||
ManifestDigest: img.Metadata.ManifestDigest,
|
||||
Size: img.Metadata.Size,
|
||||
MediaType: string(img.Metadata.MediaType),
|
||||
Tags: tags,
|
||||
Layers: make([]StereoscopeLayerMetadata, len(img.Layers)),
|
||||
RawConfig: img.Metadata.RawConfig,
|
||||
RawManifest: img.Metadata.RawManifest,
|
||||
RepoDigests: img.Metadata.RepoDigests,
|
||||
Architecture: img.Metadata.Architecture,
|
||||
Variant: img.Metadata.Variant,
|
||||
OS: img.Metadata.OS,
|
||||
Labels: img.Metadata.Config.Config.Labels,
|
||||
}
|
||||
|
||||
// populate image metadata
|
||||
for idx, l := range img.Layers {
|
||||
theImg.Layers[idx] = StereoscopeLayerMetadata{
|
||||
MediaType: string(l.Metadata.MediaType),
|
||||
Digest: l.Metadata.Digest,
|
||||
Size: l.Metadata.Size,
|
||||
}
|
||||
}
|
||||
return theImg
|
||||
}
|
|
@ -1,90 +1,53 @@
|
|||
package source
|
||||
package stereoscopesource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/anchore/syft/syft/source/internal"
|
||||
)
|
||||
|
||||
var _ Source = (*StereoscopeImageSource)(nil)
|
||||
var _ source.Source = (*stereoscopeImageSource)(nil)
|
||||
|
||||
type StereoscopeImageConfig struct {
|
||||
type ImageConfig struct {
|
||||
Reference string
|
||||
From image.Source
|
||||
Platform *image.Platform
|
||||
RegistryOptions *image.RegistryOptions
|
||||
Exclude ExcludeConfig
|
||||
Alias Alias
|
||||
Exclude source.ExcludeConfig
|
||||
Alias source.Alias
|
||||
}
|
||||
|
||||
type StereoscopeImageSource struct {
|
||||
type stereoscopeImageSource struct {
|
||||
id artifact.ID
|
||||
config StereoscopeImageConfig
|
||||
config ImageConfig
|
||||
image *image.Image
|
||||
metadata StereoscopeImageSourceMetadata
|
||||
metadata source.ImageMetadata
|
||||
}
|
||||
|
||||
func NewFromStereoscopeImageObject(img *image.Image, reference string, alias *Alias) (*StereoscopeImageSource, error) {
|
||||
var aliasVal Alias
|
||||
if !alias.IsEmpty() {
|
||||
aliasVal = *alias
|
||||
}
|
||||
cfg := StereoscopeImageConfig{
|
||||
Reference: reference,
|
||||
Alias: aliasVal,
|
||||
}
|
||||
func New(img *image.Image, cfg ImageConfig) source.Source {
|
||||
metadata := imageMetadataFromStereoscopeImage(img, cfg.Reference)
|
||||
|
||||
return &StereoscopeImageSource{
|
||||
return &stereoscopeImageSource{
|
||||
id: deriveIDFromStereoscopeImage(cfg.Alias, metadata),
|
||||
config: cfg,
|
||||
image: img,
|
||||
metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromStereoscopeImage(cfg StereoscopeImageConfig) (*StereoscopeImageSource, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
var opts []stereoscope.Option
|
||||
if cfg.RegistryOptions != nil {
|
||||
opts = append(opts, stereoscope.WithRegistryOptions(*cfg.RegistryOptions))
|
||||
}
|
||||
|
||||
if cfg.Platform != nil {
|
||||
opts = append(opts, stereoscope.WithPlatform(cfg.Platform.String()))
|
||||
}
|
||||
|
||||
img, err := stereoscope.GetImageFromSource(ctx, cfg.Reference, cfg.From, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load image: %w", err)
|
||||
}
|
||||
|
||||
metadata := imageMetadataFromStereoscopeImage(img, cfg.Reference)
|
||||
|
||||
return &StereoscopeImageSource{
|
||||
id: deriveIDFromStereoscopeImage(cfg.Alias, metadata),
|
||||
config: cfg,
|
||||
image: img,
|
||||
metadata: metadata,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s StereoscopeImageSource) ID() artifact.ID {
|
||||
func (s stereoscopeImageSource) ID() artifact.ID {
|
||||
return s.id
|
||||
}
|
||||
|
||||
func (s StereoscopeImageSource) Describe() Description {
|
||||
func (s stereoscopeImageSource) Describe() source.Description {
|
||||
a := s.config.Alias
|
||||
|
||||
name := a.Name
|
||||
|
@ -123,7 +86,7 @@ func (s StereoscopeImageSource) Describe() Description {
|
|||
nameIfUnset(s.metadata.UserInput)
|
||||
versionIfUnset(s.metadata.ManifestDigest)
|
||||
|
||||
return Description{
|
||||
return source.Description{
|
||||
ID: string(s.id),
|
||||
Name: name,
|
||||
Version: version,
|
||||
|
@ -131,14 +94,14 @@ func (s StereoscopeImageSource) Describe() Description {
|
|||
}
|
||||
}
|
||||
|
||||
func (s StereoscopeImageSource) FileResolver(scope Scope) (file.Resolver, error) {
|
||||
func (s stereoscopeImageSource) FileResolver(scope source.Scope) (file.Resolver, error) {
|
||||
var res file.Resolver
|
||||
var err error
|
||||
|
||||
switch scope {
|
||||
case SquashedScope:
|
||||
case source.SquashedScope:
|
||||
res, err = fileresolver.NewFromContainerImageSquash(s.image)
|
||||
case AllLayersScope:
|
||||
case source.AllLayersScope:
|
||||
res, err = fileresolver.NewFromContainerImageAllLayers(s.image)
|
||||
default:
|
||||
return nil, fmt.Errorf("bad image scope provided: %+v", scope)
|
||||
|
@ -156,29 +119,29 @@ func (s StereoscopeImageSource) FileResolver(scope Scope) (file.Resolver, error)
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (s StereoscopeImageSource) Close() error {
|
||||
func (s stereoscopeImageSource) Close() error {
|
||||
if s.image == nil {
|
||||
return nil
|
||||
}
|
||||
return s.image.Cleanup()
|
||||
}
|
||||
|
||||
func imageMetadataFromStereoscopeImage(img *image.Image, reference string) StereoscopeImageSourceMetadata {
|
||||
func imageMetadataFromStereoscopeImage(img *image.Image, reference string) source.ImageMetadata {
|
||||
tags := make([]string, len(img.Metadata.Tags))
|
||||
for idx, tag := range img.Metadata.Tags {
|
||||
tags[idx] = tag.String()
|
||||
}
|
||||
|
||||
layers := make([]StereoscopeLayerMetadata, len(img.Layers))
|
||||
layers := make([]source.LayerMetadata, len(img.Layers))
|
||||
for idx, l := range img.Layers {
|
||||
layers[idx] = StereoscopeLayerMetadata{
|
||||
layers[idx] = source.LayerMetadata{
|
||||
MediaType: string(l.Metadata.MediaType),
|
||||
Digest: l.Metadata.Digest,
|
||||
Size: l.Metadata.Size,
|
||||
}
|
||||
}
|
||||
|
||||
return StereoscopeImageSourceMetadata{
|
||||
return source.ImageMetadata{
|
||||
ID: img.Metadata.ID,
|
||||
UserInput: reference,
|
||||
ManifestDigest: img.Metadata.ManifestDigest,
|
||||
|
@ -203,7 +166,7 @@ func imageMetadataFromStereoscopeImage(img *image.Image, reference string) Stere
|
|||
//
|
||||
// in all cases, if an alias is provided, it is additionally considered in the ID calculation. This allows for the
|
||||
// same image to be scanned multiple times with different aliases and be considered logically different.
|
||||
func deriveIDFromStereoscopeImage(alias Alias, metadata StereoscopeImageSourceMetadata) artifact.ID {
|
||||
func deriveIDFromStereoscopeImage(alias source.Alias, metadata source.ImageMetadata) artifact.ID {
|
||||
var input string
|
||||
|
||||
if len(metadata.RawManifest) > 0 {
|
||||
|
@ -226,10 +189,10 @@ func deriveIDFromStereoscopeImage(alias Alias, metadata StereoscopeImageSourceMe
|
|||
input = digest.Canonical.FromString(input + aliasStr).String()
|
||||
}
|
||||
|
||||
return artifactIDFromDigest(input)
|
||||
return internal.ArtifactIDFromDigest(input)
|
||||
}
|
||||
|
||||
func calculateChainID(lm []StereoscopeLayerMetadata) string {
|
||||
func calculateChainID(lm []source.LayerMetadata) string {
|
||||
if len(lm) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
@ -242,7 +205,7 @@ func calculateChainID(lm []StereoscopeLayerMetadata) string {
|
|||
return id
|
||||
}
|
||||
|
||||
func chain(chainID string, layers []StereoscopeLayerMetadata) string {
|
||||
func chain(chainID string, layers []source.LayerMetadata) string {
|
||||
if len(layers) < 1 {
|
||||
return chainID
|
||||
}
|
61
syft/source/stereoscopesource/image_source_provider.go
Normal file
61
syft/source/stereoscopesource/image_source_provider.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package stereoscopesource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const ImageTag = "image"
|
||||
|
||||
type ProviderConfig struct {
|
||||
StereoscopeImageProviderConfig stereoscope.ImageProviderConfig
|
||||
Exclude source.ExcludeConfig
|
||||
Alias source.Alias
|
||||
}
|
||||
|
||||
type stereoscopeImageSourceProvider struct {
|
||||
stereoscopeProvider image.Provider
|
||||
cfg ProviderConfig
|
||||
}
|
||||
|
||||
var _ source.Provider = (*stereoscopeImageSourceProvider)(nil)
|
||||
|
||||
func (l stereoscopeImageSourceProvider) Name() string {
|
||||
return l.stereoscopeProvider.Name()
|
||||
}
|
||||
|
||||
func (l stereoscopeImageSourceProvider) Provide(ctx context.Context) (source.Source, error) {
|
||||
img, err := l.stereoscopeProvider.Provide(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := ImageConfig{
|
||||
Reference: l.cfg.StereoscopeImageProviderConfig.UserInput,
|
||||
Platform: l.cfg.StereoscopeImageProviderConfig.Platform,
|
||||
RegistryOptions: &l.cfg.StereoscopeImageProviderConfig.Registry,
|
||||
Exclude: l.cfg.Exclude,
|
||||
Alias: l.cfg.Alias,
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(img, cfg), nil
|
||||
}
|
||||
|
||||
func Providers(cfg ProviderConfig) []collections.TaggedValue[source.Provider] {
|
||||
stereoscopeProviders := collections.TaggedValueSet[source.Provider]{}
|
||||
providers := stereoscope.ImageProviders(cfg.StereoscopeImageProviderConfig)
|
||||
for _, provider := range providers {
|
||||
var sourceProvider source.Provider = stereoscopeImageSourceProvider{
|
||||
stereoscopeProvider: provider.Value,
|
||||
cfg: cfg,
|
||||
}
|
||||
stereoscopeProviders = append(stereoscopeProviders,
|
||||
collections.NewTaggedValue(sourceProvider, append([]string{provider.Value.Name(), ImageTag}, provider.Tags...)...))
|
||||
}
|
||||
return stereoscopeProviders
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package source
|
||||
package stereoscopesource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
@ -9,12 +10,16 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/internal/testutil"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_StereoscopeImage_Exclusions(t *testing.T) {
|
||||
testutil.Chdir(t, "..") // run with source/test-fixtures
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
input string
|
||||
|
@ -76,22 +81,27 @@ func Test_StereoscopeImage_Exclusions(t *testing.T) {
|
|||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
src, err := NewFromStereoscopeImage(
|
||||
StereoscopeImageConfig{
|
||||
Reference: strings.SplitN(imagetest.PrepareFixtureImage(t, "docker-archive", test.input), ":", 2)[1],
|
||||
From: image.DockerTarballSource,
|
||||
Exclude: ExcludeConfig{
|
||||
imageName := strings.SplitN(imagetest.PrepareFixtureImage(t, "docker-archive", test.input), ":", 2)[1]
|
||||
|
||||
img, err := stereoscope.GetImage(context.TODO(), imageName)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, img)
|
||||
|
||||
src := New(
|
||||
img,
|
||||
ImageConfig{
|
||||
Reference: imageName,
|
||||
Exclude: source.ExcludeConfig{
|
||||
Paths: test.exclusions,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, src.Close())
|
||||
})
|
||||
|
||||
res, err := src.FileResolver(SquashedScope)
|
||||
res, err := src.FileResolver(source.SquashedScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
contents, err := res.FilesByGlob(test.glob)
|
||||
|
@ -105,15 +115,15 @@ func Test_StereoscopeImage_Exclusions(t *testing.T) {
|
|||
func Test_StereoscopeImageSource_ID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
alias Alias
|
||||
metadata StereoscopeImageSourceMetadata
|
||||
alias source.Alias
|
||||
metadata source.ImageMetadata
|
||||
want artifact.ID
|
||||
}{
|
||||
{
|
||||
name: "use raw manifest over chain ID or user input",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
Layers: []StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
Digest: "a",
|
||||
},
|
||||
|
@ -134,9 +144,9 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "use chain ID over user input",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
//UserInput: "user-input",
|
||||
Layers: []StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
Digest: "a",
|
||||
},
|
||||
|
@ -149,7 +159,7 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
},
|
||||
want: func() artifact.ID {
|
||||
metadata := []StereoscopeLayerMetadata{
|
||||
metadata := []source.LayerMetadata{
|
||||
{
|
||||
Digest: "a",
|
||||
},
|
||||
|
@ -165,7 +175,7 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "use user input last",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
},
|
||||
want: func() artifact.ID {
|
||||
|
@ -176,9 +186,9 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "without alias (first)",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
Layers: []StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
Digest: "a",
|
||||
},
|
||||
|
@ -195,13 +205,13 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "always consider alias (first)",
|
||||
alias: Alias{
|
||||
alias: source.Alias{
|
||||
Name: "alias",
|
||||
Version: "version",
|
||||
},
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
Layers: []StereoscopeLayerMetadata{
|
||||
Layers: []source.LayerMetadata{
|
||||
{
|
||||
Digest: "a",
|
||||
},
|
||||
|
@ -218,18 +228,18 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "without alias (last)",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
},
|
||||
want: "ab0dff627d80b9753193d7280bec8f45e8ec6b4cb0912c6fffcf7cd782d9739e",
|
||||
},
|
||||
{
|
||||
name: "always consider alias (last)",
|
||||
alias: Alias{
|
||||
alias: source.Alias{
|
||||
Name: "alias",
|
||||
Version: "version",
|
||||
},
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
},
|
||||
want: "fe86c0eecd5654d3c0c0b2176aa394aef6440347c241aa8d9b628dfdde4287cf",
|
||||
|
@ -245,18 +255,18 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
|||
func Test_Describe(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source StereoscopeImageSource
|
||||
expected Description
|
||||
source stereoscopeImageSource
|
||||
expected source.Description
|
||||
}{
|
||||
{
|
||||
name: "name from user input",
|
||||
source: StereoscopeImageSource{
|
||||
source: stereoscopeImageSource{
|
||||
id: "some-id",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
metadata: source.ImageMetadata{
|
||||
UserInput: "user input",
|
||||
},
|
||||
},
|
||||
expected: Description{
|
||||
expected: source.Description{
|
||||
ID: "some-id",
|
||||
Name: "user input",
|
||||
},
|
|
@ -351,7 +351,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
|||
func TestRegistryAuth(t *testing.T) {
|
||||
host := "localhost:17"
|
||||
image := fmt.Sprintf("%s/something:latest", host)
|
||||
args := []string{"scan", "-vvv", fmt.Sprintf("registry:%s", image)}
|
||||
args := []string{"scan", "-vvv", image, "--from", "registry"}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -363,7 +363,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
name: "fallback to keychain",
|
||||
args: args,
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("from registry"),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf("no registry credentials configured for %q, using the default keychain", host)),
|
||||
},
|
||||
|
@ -377,7 +377,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
"SYFT_REGISTRY_AUTH_PASSWORD": "password",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("from registry"),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf(`using basic auth for registry "%s"`, host)),
|
||||
},
|
||||
|
@ -390,7 +390,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
"SYFT_REGISTRY_AUTH_TOKEN": "my-token",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("from registry"),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf(`using token for registry "%s"`, host)),
|
||||
},
|
||||
|
@ -402,7 +402,7 @@ func TestRegistryAuth(t *testing.T) {
|
|||
"SYFT_REGISTRY_AUTH_AUTHORITY": host,
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("source=OciRegistry"),
|
||||
assertInOutput("from registry"),
|
||||
assertInOutput(image),
|
||||
assertInOutput(fmt.Sprintf(`no registry credentials configured for %q, using the default keychain`, host)),
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue