Files
syft/syft/source/detection.go
T
23e3de75e3 Add containerd support (#1793)
* [wip] add containerd UI handlers

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

* Add containerd support

- Add UI handlers (done by @wagoodman)
- Add containerd types and wrappers (done by @wagoodman)
- Add flag for specifying containerd address

Closes #201

Signed-off-by: Shane Dell <shanedell100@gmail.com>

* Fix lint

Signed-off-by: Shane Dell <shanedell100@gmail.com>

* add containerd ui handler

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add containerd scheme to readme

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* add test for scheme detection

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
Signed-off-by: Shane Dell <shanedell100@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
2023-09-18 11:33:43 -04:00

208 lines
5.5 KiB
Go

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 packages 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
}
}