syft/syft/get_source.go
Keith Zantow a978966cad
feat: add --from flag, refactor source providers (#2610)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2024-02-27 16:44:37 -05:00

102 lines
2.3 KiB
Go

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
}