Projects STRLCPY syft Commits d49ba172
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■
    cmd/syft/cli/attest/attest.go
    skipped 2 lines
    3 3  import (
    4 4   "context"
    5 5   "fmt"
     6 + "github.com/anchore/syft/syft/source/scheme"
    6 7   "os"
    7 8   "os/exec"
    8 9   "strings"
    skipped 43 lines
    52 53   return fmt.Errorf("could not generate source input for packages command: %w", err)
    53 54   }
    54 55   
    55  - if si.Scheme != source.ImageScheme {
     56 + if si.Scheme != scheme.ContainerImageScheme {
    56 57   return fmt.Errorf("attestations are only supported for oci images at this time")
    57 58   }
    58 59   
    skipped 187 lines
  • ■ ■ ■ ■ ■ ■
    cmd/syft/cli/eventloop/tasks.go
    skipped 11 lines
    12 12   "github.com/anchore/syft/syft/source"
    13 13  )
    14 14   
    15  -type Task func(*sbom.Artifacts, *source.Source) ([]artifact.Relationship, error)
     15 +type Task func(*sbom.Artifacts, source.Source) ([]artifact.Relationship, error)
    16 16   
    17 17  func Tasks(app *config.Application) ([]Task, error) {
    18 18   var tasks []Task
    skipped 25 lines
    44 44   return nil, nil
    45 45   }
    46 46   
    47  - task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
     47 + task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
    48 48   packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig())
    49 49   
    50 50   results.PackageCatalog = packageCatalog
    skipped 12 lines
    63 63   
    64 64   metadataCataloger := file.NewMetadataCataloger()
    65 65   
    66  - task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
     66 + task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
    67 67   resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
    68 68   if err != nil {
    69 69   return nil, err
    skipped 39 lines
    109 109   return nil, err
    110 110   }
    111 111   
    112  - task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
     112 + task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
    113 113   resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
    114 114   if err != nil {
    115 115   return nil, err
    skipped 25 lines
    141 141   return nil, err
    142 142   }
    143 143   
    144  - task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
     144 + task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
    145 145   resolver, err := src.FileResolver(app.Secrets.Cataloger.ScopeOpt)
    146 146   if err != nil {
    147 147   return nil, err
    skipped 20 lines
    168 168   return nil, err
    169 169   }
    170 170   
    171  - task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
     171 + task := func(results *sbom.Artifacts, src source.Source) ([]artifact.Relationship, error) {
    172 172   resolver, err := src.FileResolver(app.FileContents.Cataloger.ScopeOpt)
    173 173   if err != nil {
    174 174   return nil, err
    skipped 10 lines
    185 185   return task, nil
    186 186  }
    187 187   
    188  -func RunTask(t Task, a *sbom.Artifacts, src *source.Source, c chan<- artifact.Relationship, errs chan<- error) {
     188 +func RunTask(t Task, a *sbom.Artifacts, src source.Source, c chan<- artifact.Relationship, errs chan<- error) {
    189 189   defer close(c)
    190 190   
    191 191   relationships, err := t(a, src)
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    cmd/syft/cli/packages/packages.go
    skipped 2 lines
    3 3  import (
    4 4   "context"
    5 5   "fmt"
     6 + "github.com/anchore/syft/syft/source/scheme"
    6 7   
    7 8   "github.com/wagoodman/go-partybus"
    8 9   
    skipped 33 lines
    42 43   
    43 44   // could be an image or a directory, with or without a scheme
    44 45   userInput := args[0]
    45  - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
     46 + si, err := scheme.Parse(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
    46 47   if err != nil {
    47 48   return fmt.Errorf("could not generate source input for packages command: %w", err)
    48 49   }
    skipped 12 lines
    61 62   )
    62 63  }
    63 64   
    64  -func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
     65 +func execWorker(app *config.Application, si scheme.Input, writer sbom.Writer) <-chan error {
    65 66   errs := make(chan error)
    66 67   go func() {
    67 68   defer close(errs)
    68 69   
    69  - src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
    70  - if cleanup != nil {
    71  - defer cleanup()
     70 + src, err := scheme.NewSource(si, app.Registry.ToOptions(), app.Exclusions)
     71 + if src != nil {
     72 + defer src.Close()
    72 73   }
    73 74   if err != nil {
    74 75   errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
    skipped 18 lines
    93 94   return errs
    94 95  }
    95 96   
    96  -func GenerateSBOM(src *source.Source, errs chan error, app *config.Application) (*sbom.SBOM, error) {
     97 +func GenerateSBOM(src source.Source, errs chan error, app *config.Application) (*sbom.SBOM, error) {
    97 98   tasks, err := eventloop.Tasks(app)
    98 99   if err != nil {
    99 100   return nil, err
    100 101   }
    101 102   
    102 103   s := sbom.SBOM{
    103  - Source: src.Metadata,
     104 + Source: src.Describe(),
    104 105   Descriptor: sbom.Descriptor{
    105 106   Name: internal.ApplicationName,
    106 107   Version: version.FromBuild().Version,
    skipped 6 lines
    113 114   return &s, nil
    114 115  }
    115 116   
    116  -func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []eventloop.Task, errs chan error) {
     117 +func buildRelationships(s *sbom.SBOM, src source.Source, tasks []eventloop.Task, errs chan error) {
    117 118   var relationships []<-chan artifact.Relationship
    118 119   for _, task := range tasks {
    119 120   c := make(chan artifact.Relationship)
    skipped 33 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/decoder.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "io"
    6 7   
    7 8   "github.com/CycloneDX/cyclonedx-go"
    skipped 236 lines
    244 245   switch c.Type {
    245 246   case cyclonedx.ComponentTypeContainer:
    246 247   return source.Metadata{
    247  - Scheme: source.ImageScheme,
     248 + Scheme: scheme.ContainerImageScheme,
    248 249   ImageMetadata: image,
    249 250   }
    250 251   case cyclonedx.ComponentTypeFile:
    251 252   return source.Metadata{
    252  - Scheme: source.FileScheme, // or source.DirectoryScheme
     253 + Scheme: scheme.FileScheme, // or source.DirectoryScheme
    253 254   Path: c.Name,
    254 255   ImageMetadata: image,
    255 256   }
    skipped 19 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/format.go
    1 1  package cyclonedxhelpers
    2 2   
    3 3  import (
     4 + "github.com/anchore/syft/syft/source/scheme"
    4 5   "time"
    5 6   
    6 7   "github.com/CycloneDX/cyclonedx-go"
    skipped 166 lines
    173 174  func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component {
    174 175   name := srcMetadata.Name
    175 176   switch srcMetadata.Scheme {
    176  - case source.ImageScheme:
     177 + case scheme.ContainerImageScheme:
    177 178   if name == "" {
    178 179   name = srcMetadata.ImageMetadata.UserInput
    179 180   }
    skipped 7 lines
    187 188   Name: name,
    188 189   Version: srcMetadata.ImageMetadata.ManifestDigest,
    189 190   }
    190  - case source.DirectoryScheme, source.FileScheme:
     191 + case scheme.DirectoryScheme, scheme.FileScheme:
    191 192   if name == "" {
    192 193   name = srcMetadata.Path
    193 194   }
    skipped 14 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/document_name.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/syft/syft/source"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6  )
    6 7   
    7 8  func DocumentName(srcMetadata source.Metadata) string {
    skipped 2 lines
    10 11   }
    11 12   
    12 13   switch srcMetadata.Scheme {
    13  - case source.ImageScheme:
     14 + case scheme.ContainerImageScheme:
    14 15   return srcMetadata.ImageMetadata.UserInput
    15  - case source.DirectoryScheme, source.FileScheme:
     16 + case scheme.DirectoryScheme, scheme.FileScheme:
    16 17   return srcMetadata.Path
    17 18   default:
    18 19   return "unknown"
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/document_name_test.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "strings"
    6 7   "testing"
    7 8   
    skipped 5 lines
    13 14   
    14 15  func Test_DocumentName(t *testing.T) {
    15 16   allSchemes := strset.New()
    16  - for _, s := range source.AllSchemes {
     17 + for _, s := range scheme.AllSchemes {
    17 18   allSchemes.Add(string(s))
    18 19   }
    19 20   testedSchemes := strset.New()
    skipped 8 lines
    28 29   name: "image",
    29 30   inputName: "my-name",
    30 31   srcMetadata: source.Metadata{
    31  - Scheme: source.ImageScheme,
     32 + Scheme: scheme.ContainerImageScheme,
    32 33   ImageMetadata: source.ImageMetadata{
    33 34   UserInput: "image-repo/name:tag",
    34 35   ID: "id",
    skipped 6 lines
    41 42   name: "directory",
    42 43   inputName: "my-name",
    43 44   srcMetadata: source.Metadata{
    44  - Scheme: source.DirectoryScheme,
     45 + Scheme: scheme.DirectoryScheme,
    45 46   Path: "some/path/to/place",
    46 47   },
    47 48   expected: "some/path/to/place",
    skipped 2 lines
    50 51   name: "file",
    51 52   inputName: "my-name",
    52 53   srcMetadata: source.Metadata{
    53  - Scheme: source.FileScheme,
     54 + Scheme: scheme.FileScheme,
    54 55   Path: "some/path/to/place",
    55 56   },
    56 57   expected: "some/path/to/place",
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/document_namespace.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "net/url"
    6 7   "path"
    7 8   "strings"
    skipped 19 lines
    27 28   name = cleanName(name)
    28 29   input := "unknown-source-type"
    29 30   switch srcMetadata.Scheme {
    30  - case source.ImageScheme:
     31 + case scheme.ContainerImageScheme:
    31 32   input = inputImage
    32  - case source.DirectoryScheme:
     33 + case scheme.DirectoryScheme:
    33 34   input = inputDirectory
    34  - case source.FileScheme:
     35 + case scheme.FileScheme:
    35 36   input = inputFile
    36 37   }
    37 38   
    skipped 25 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/document_namespace_test.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "strings"
    6 7   "testing"
    7 8   
    skipped 5 lines
    13 14   
    14 15  func Test_documentNamespace(t *testing.T) {
    15 16   allSchemes := strset.New()
    16  - for _, s := range source.AllSchemes {
     17 + for _, s := range scheme.AllSchemes {
    17 18   allSchemes.Add(string(s))
    18 19   }
    19 20   testedSchemes := strset.New()
    skipped 8 lines
    28 29   name: "image",
    29 30   inputName: "my-name",
    30 31   srcMetadata: source.Metadata{
    31  - Scheme: source.ImageScheme,
     32 + Scheme: scheme.ContainerImageScheme,
    32 33   ImageMetadata: source.ImageMetadata{
    33 34   UserInput: "image-repo/name:tag",
    34 35   ID: "id",
    skipped 6 lines
    41 42   name: "directory",
    42 43   inputName: "my-name",
    43 44   srcMetadata: source.Metadata{
    44  - Scheme: source.DirectoryScheme,
     45 + Scheme: scheme.DirectoryScheme,
    45 46   Path: "some/path/to/place",
    46 47   },
    47 48   expected: "https://anchore.com/syft/dir/my-name-",
    skipped 2 lines
    50 51   name: "file",
    51 52   inputName: "my-name",
    52 53   srcMetadata: source.Metadata{
    53  - Scheme: source.FileScheme,
     54 + Scheme: scheme.FileScheme,
    54 55   Path: "some/path/to/place",
    55 56   },
    56 57   expected: "https://anchore.com/syft/file/my-name-",
    skipped 17 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_syft_model.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "errors"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "net/url"
    6 7   "strconv"
    7 8   "strings"
    skipped 19 lines
    27 28   
    28 29   spdxIDMap := make(map[string]interface{})
    29 30   
    30  - src := source.Metadata{Scheme: source.UnknownScheme}
     31 + src := source.Metadata{Scheme: scheme.UnknownScheme}
    31 32   src.Scheme = extractSchemeFromNamespace(doc.DocumentNamespace)
    32 33   
    33 34   s := &sbom.SBOM{
    skipped 19 lines
    53 54  // image, directory, for example. This is our best effort to determine
    54 55  // the scheme. Syft-generated SBOMs have in the namespace
    55 56  // field a type encoded, which we try to identify here.
    56  -func extractSchemeFromNamespace(ns string) source.Scheme {
     57 +func extractSchemeFromNamespace(ns string) scheme.Scheme {
    57 58   u, err := url.Parse(ns)
    58 59   if err != nil {
    59  - return source.UnknownScheme
     60 + return scheme.UnknownScheme
    60 61   }
    61 62   
    62 63   parts := strings.Split(u.Path, "/")
    63 64   for _, p := range parts {
    64 65   switch p {
    65 66   case inputFile:
    66  - return source.FileScheme
     67 + return scheme.FileScheme
    67 68   case inputImage:
    68  - return source.ImageScheme
     69 + return scheme.ContainerImageScheme
    69 70   case inputDirectory:
    70  - return source.DirectoryScheme
     71 + return scheme.DirectoryScheme
    71 72   }
    72 73   }
    73  - return source.UnknownScheme
     74 + return scheme.UnknownScheme
    74 75  }
    75 76   
    76 77  func findLinuxReleaseByPURL(doc *spdx.Document) *linux.Release {
    skipped 334 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_syft_model_test.go
    1 1  package spdxhelpers
    2 2   
    3 3  import (
     4 + "github.com/anchore/syft/syft/source/scheme"
    4 5   "testing"
    5 6   
    6 7   "github.com/spdx/tools-golang/spdx"
    skipped 189 lines
    196 197  func TestExtractSourceFromNamespaces(t *testing.T) {
    197 198   tests := []struct {
    198 199   namespace string
    199  - expected source.Scheme
     200 + expected scheme.Scheme
    200 201   }{
    201 202   {
    202 203   namespace: "https://anchore.com/syft/file/d42b01d0-7325-409b-b03f-74082935c4d3",
    203  - expected: source.FileScheme,
     204 + expected: scheme.FileScheme,
    204 205   },
    205 206   {
    206 207   namespace: "https://anchore.com/syft/image/d42b01d0-7325-409b-b03f-74082935c4d3",
    207  - expected: source.ImageScheme,
     208 + expected: scheme.ContainerImageScheme,
    208 209   },
    209 210   {
    210 211   namespace: "https://anchore.com/syft/dir/d42b01d0-7325-409b-b03f-74082935c4d3",
    211  - expected: source.DirectoryScheme,
     212 + expected: scheme.DirectoryScheme,
    212 213   },
    213 214   {
    214 215   namespace: "https://another-host/blob/123",
    215  - expected: source.UnknownScheme,
     216 + expected: scheme.UnknownScheme,
    216 217   },
    217 218   {
    218 219   namespace: "bla bla",
    219  - expected: source.UnknownScheme,
     220 + expected: scheme.UnknownScheme,
    220 221   },
    221 222   {
    222 223   namespace: "",
    223  - expected: source.UnknownScheme,
     224 + expected: scheme.UnknownScheme,
    224 225   },
    225 226   }
    226 227   
    skipped 196 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/github/encoder.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "strings"
    6 7   "time"
    7 8   
    skipped 77 lines
    85 86   }
    86 87   packagePath = strings.TrimPrefix(packagePath, "/")
    87 88   switch s.Scheme {
    88  - case source.ImageScheme:
     89 + case scheme.ContainerImageScheme:
    89 90   image := strings.ReplaceAll(s.ImageMetadata.UserInput, ":/", "//")
    90 91   return fmt.Sprintf("%s:/%s", image, packagePath)
    91  - case source.FileScheme:
     92 + case scheme.FileScheme:
    92 93   if isArchive(inputPath) {
    93 94   return fmt.Sprintf("%s:/%s", inputPath, packagePath)
    94 95   }
    95 96   return inputPath
    96  - case source.DirectoryScheme:
     97 + case scheme.DirectoryScheme:
    97 98   if inputPath != "" {
    98 99   return fmt.Sprintf("%s/%s", inputPath, packagePath)
    99 100   }
    skipped 86 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/github/encoder_test.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "encoding/json"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "testing"
    6 7   
    7 8   "github.com/stretchr/testify/assert"
    skipped 8 lines
    16 17  func Test_toGithubModel(t *testing.T) {
    17 18   s := sbom.SBOM{
    18 19   Source: source.Metadata{
    19  - Scheme: source.ImageScheme,
     20 + Scheme: scheme.ContainerImageScheme,
    20 21   ImageMetadata: source.ImageMetadata{
    21 22   UserInput: "ubuntu:18.04",
    22 23   Architecture: "amd64",
    skipped 112 lines
    135 136   
    136 137   // Just test the other schemes:
    137 138   s.Source.Path = "."
    138  - s.Source.Scheme = source.DirectoryScheme
     139 + s.Source.Scheme = scheme.DirectoryScheme
    139 140   actual = toGithubModel(&s)
    140 141   assert.Equal(t, "etc", actual.Manifests["etc"].Name)
    141 142   
    142 143   s.Source.Path = "./artifacts"
    143  - s.Source.Scheme = source.DirectoryScheme
     144 + s.Source.Scheme = scheme.DirectoryScheme
    144 145   actual = toGithubModel(&s)
    145 146   assert.Equal(t, "artifacts/etc", actual.Manifests["artifacts/etc"].Name)
    146 147   
    147 148   s.Source.Path = "/artifacts"
    148  - s.Source.Scheme = source.DirectoryScheme
     149 + s.Source.Scheme = scheme.DirectoryScheme
    149 150   actual = toGithubModel(&s)
    150 151   assert.Equal(t, "/artifacts/etc", actual.Manifests["/artifacts/etc"].Name)
    151 152   
    152 153   s.Source.Path = "./executable"
    153  - s.Source.Scheme = source.FileScheme
     154 + s.Source.Scheme = scheme.FileScheme
    154 155   actual = toGithubModel(&s)
    155 156   assert.Equal(t, "executable", actual.Manifests["executable"].Name)
    156 157   
    157 158   s.Source.Path = "./archive.tar.gz"
    158  - s.Source.Scheme = source.FileScheme
     159 + s.Source.Scheme = scheme.FileScheme
    159 160   actual = toGithubModel(&s)
    160 161   assert.Equal(t, "archive.tar.gz:/etc", actual.Manifests["archive.tar.gz:/etc"].Name)
    161 162  }
    skipped 1 lines
  • ■ ■ ■ ■ ■
    syft/formats/spdxtagvalue/encoder_test.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "flag"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "regexp"
    6 7   "testing"
    7 8   
    skipped 45 lines
    53 54   },
    54 55   Relationships: nil,
    55 56   Source: source.Metadata{
    56  - Scheme: source.DirectoryScheme,
     57 + Scheme: scheme.DirectoryScheme,
    57 58   Path: "foobar/baz", // in this case, foobar is used as the spdx docment name
    58 59   },
    59 60   Descriptor: sbom.Descriptor{
    skipped 38 lines
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/encoder_test.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "flag"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "regexp"
    6 7   "testing"
    7 8   
    skipped 158 lines
    166 167   },
    167 168   Source: source.Metadata{
    168 169   ID: "c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
    169  - Scheme: source.ImageScheme,
     170 + Scheme: scheme.ContainerImageScheme,
    170 171   ImageMetadata: source.ImageMetadata{
    171 172   UserInput: "user-image-input",
    172 173   ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
    skipped 43 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_format_model.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "sort"
    6 7   "strconv"
    7 8   
    skipped 233 lines
    241 242  // toSourceModel creates a new source object to be represented into JSON.
    242 243  func toSourceModel(src source.Metadata) (model.Source, error) {
    243 244   switch src.Scheme {
    244  - case source.ImageScheme:
     245 + case scheme.ContainerImageScheme:
    245 246   metadata := src.ImageMetadata
    246 247   // ensure that empty collections are not shown as null
    247 248   if metadata.RepoDigests == nil {
    skipped 7 lines
    255 256   Type: "image",
    256 257   Target: metadata,
    257 258   }, nil
    258  - case source.DirectoryScheme:
     259 + case scheme.DirectoryScheme:
    259 260   return model.Source{
    260 261   ID: src.ID,
    261 262   Type: "directory",
    262 263   Target: src.Path,
    263 264   }, nil
    264  - case source.FileScheme:
     265 + case scheme.FileScheme:
    265 266   return model.Source{
    266 267   ID: src.ID,
    267 268   Type: "file",
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_format_model_test.go
    1 1  package syftjson
    2 2   
    3 3  import (
     4 + "github.com/anchore/syft/syft/source/scheme"
    4 5   "testing"
    5 6   
    6 7   "github.com/scylladb/go-set/strset"
    skipped 7 lines
    14 15   
    15 16  func Test_toSourceModel(t *testing.T) {
    16 17   allSchemes := strset.New()
    17  - for _, s := range source.AllSchemes {
     18 + for _, s := range scheme.AllSchemes {
    18 19   allSchemes.Add(string(s))
    19 20   }
    20 21   testedSchemes := strset.New()
    skipped 7 lines
    28 29   name: "directory",
    29 30   src: source.Metadata{
    30 31   ID: "test-id",
    31  - Scheme: source.DirectoryScheme,
     32 + Scheme: scheme.DirectoryScheme,
    32 33   Path: "some/path",
    33 34   },
    34 35   expected: model.Source{
    skipped 6 lines
    41 42   name: "file",
    42 43   src: source.Metadata{
    43 44   ID: "test-id",
    44  - Scheme: source.FileScheme,
     45 + Scheme: scheme.FileScheme,
    45 46   Path: "some/path",
    46 47   },
    47 48   expected: model.Source{
    skipped 6 lines
    54 55   name: "image",
    55 56   src: source.Metadata{
    56 57   ID: "test-id",
    57  - Scheme: source.ImageScheme,
     58 + Scheme: scheme.ContainerImageScheme,
    58 59   ImageMetadata: source.ImageMetadata{
    59 60   UserInput: "user-input",
    60 61   ID: "id...",
    skipped 95 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_syft_model.go
    1 1  package syftjson
    2 2   
    3 3  import (
     4 + "github.com/anchore/syft/syft/source/scheme"
    4 5   "os"
    5 6   "strconv"
    6 7   "strings"
    skipped 220 lines
    227 228   }
    228 229   return &source.Metadata{
    229 230   ID: s.ID,
    230  - Scheme: source.DirectoryScheme,
     231 + Scheme: scheme.DirectoryScheme,
    231 232   Path: path,
    232 233   }
    233 234   case "file":
    skipped 4 lines
    238 239   }
    239 240   return &source.Metadata{
    240 241   ID: s.ID,
    241  - Scheme: source.FileScheme,
     242 + Scheme: scheme.FileScheme,
    242 243   Path: path,
    243 244   }
    244 245   case "image":
    skipped 4 lines
    249 250   }
    250 251   return &source.Metadata{
    251 252   ID: s.ID,
    252  - Scheme: source.ImageScheme,
     253 + Scheme: scheme.ContainerImageScheme,
    253 254   ImageMetadata: metadata,
    254 255   }
    255 256   }
    skipped 51 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_syft_model_test.go
    1 1  package syftjson
    2 2   
    3 3  import (
     4 + "github.com/anchore/syft/syft/source/scheme"
    4 5   "testing"
    5 6   
    6 7   "github.com/scylladb/go-set/strset"
    skipped 9 lines
    16 17   
    17 18  func Test_toSyftSourceData(t *testing.T) {
    18 19   allSchemes := strset.New()
    19  - for _, s := range source.AllSchemes {
     20 + for _, s := range scheme.AllSchemes {
    20 21   allSchemes.Add(string(s))
    21 22   }
    22 23   testedSchemes := strset.New()
    skipped 6 lines
    29 30   {
    30 31   name: "directory",
    31 32   expected: source.Metadata{
    32  - Scheme: source.DirectoryScheme,
     33 + Scheme: scheme.DirectoryScheme,
    33 34   Path: "some/path",
    34 35   },
    35 36   src: model.Source{
    skipped 4 lines
    40 41   {
    41 42   name: "file",
    42 43   expected: source.Metadata{
    43  - Scheme: source.FileScheme,
     44 + Scheme: scheme.FileScheme,
    44 45   Path: "some/path",
    45 46   },
    46 47   src: model.Source{
    skipped 4 lines
    51 52   {
    52 53   name: "image",
    53 54   expected: source.Metadata{
    54  - Scheme: source.ImageScheme,
     55 + Scheme: scheme.ContainerImageScheme,
    55 56   ImageMetadata: source.ImageMetadata{
    56 57   UserInput: "user-input",
    57 58   ID: "id...",
    skipped 174 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/text/encoder.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "fmt"
     5 + "github.com/anchore/syft/syft/source/scheme"
    5 6   "io"
    6 7   "text/tabwriter"
    7 8   
    8 9   "github.com/anchore/syft/syft/sbom"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func encoder(output io.Writer, s sbom.SBOM) error {
    skipped 2 lines
    15 15   w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
    16 16   
    17 17   switch s.Source.Scheme {
    18  - case source.DirectoryScheme, source.FileScheme:
     18 + case scheme.DirectoryScheme, scheme.FileScheme:
    19 19   fmt.Fprintf(w, "[Path: %s]\n", s.Source.Path)
    20  - case source.ImageScheme:
     20 + case scheme.ContainerImageScheme:
    21 21   fmt.Fprintln(w, "[Image]")
    22 22   
    23 23   for idx, l := range s.Source.ImageMetadata.Layers {
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    syft/lib.go
    skipped 33 lines
    34 34  // CatalogPackages takes an inventory of packages from the given image from a particular perspective
    35 35  // (e.g. squashed source, all-layers source). Returns the discovered set of packages, the identified Linux
    36 36  // distribution, and the source object used to wrap the data source.
    37  -func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Collection, []artifact.Relationship, *linux.Release, error) {
     37 +func CatalogPackages(src source.Source, cfg cataloger.Config) (*pkg.Collection, []artifact.Relationship, *linux.Release, error) {
    38 38   resolver, err := src.FileResolver(cfg.Search.Scope)
    39 39   if err != nil {
    40 40   return nil, nil, nil, fmt.Errorf("unable to determine resolver while cataloging packages: %w", err)
    skipped 13 lines
    54 54   catalogers = cataloger.AllCatalogers(cfg)
    55 55   } else {
    56 56   // otherwise conditionally use the correct set of loggers based on the input type (container image or directory)
    57  - switch src.Metadata.Scheme {
    58  - case source.ImageScheme:
     57 + 
     58 + // TODO: this is bad, we should not be using the concrete type to determine the cataloger set
     59 + // instead this should be a caller concern (pass the catalogers you want to use). The SBOM build PR will do this.
     60 + switch src.(type) {
     61 + 
     62 + case *source.StereoscopeImageSource:
    59 63   log.Info("cataloging image")
    60 64   catalogers = cataloger.ImageCatalogers(cfg)
    61  - case source.FileScheme:
     65 + case *source.FileSource:
    62 66   log.Info("cataloging file")
    63 67   catalogers = cataloger.AllCatalogers(cfg)
    64  - case source.DirectoryScheme:
     68 + case *source.DirectorySource:
    65 69   log.Info("cataloging directory")
    66 70   catalogers = cataloger.DirectoryCatalogers(cfg)
    67 71   default:
    68  - return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme)
     72 + // TODO: no
     73 + panic("REMOVE ME")
    69 74   }
    70 75   }
    71 76   
    skipped 4 lines
    76 81   return catalog, relationships, release, err
    77 82  }
    78 83   
    79  -func newSourceRelationshipsFromCatalog(src *source.Source, c *pkg.Collection) []artifact.Relationship {
     84 +func newSourceRelationshipsFromCatalog(src source.Source, c *pkg.Collection) []artifact.Relationship {
    80 85   relationships := make([]artifact.Relationship, 0) // Should we pre-allocate this by giving catalog a Len() method?
    81 86   for p := range c.Enumerate() {
    82 87   relationships = append(relationships, artifact.Relationship{
    skipped 19 lines
  • ■ ■ ■ ■
    syft/sbom/sbom.go
    skipped 14 lines
    15 15  type SBOM struct {
    16 16   Artifacts Artifacts
    17 17   Relationships []artifact.Relationship
    18  - Source source.Metadata
     18 + Source source.Description
    19 19   Descriptor Descriptor
    20 20  }
    21 21   
    skipped 90 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/config.go
     1 +package source
     2 + 
     3 +type ExcludeConfig struct {
     4 + Paths []string
     5 +}
     6 + 
     7 +type ImageInterpreter interface {
     8 + Metadata() ImageMetadata
     9 +}
     10 + 
     11 +type PathInterpreter interface {
     12 + Metadata() PathMetadata
     13 +}
     14 + 
     15 +type PathMetadata struct {
     16 + Path string
     17 + Base string
     18 +}
     19 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/description.go
     1 +package source
     2 + 
     3 +// Description represents any static source data that helps describe "what" was cataloged.
     4 +type Description struct {
     5 + ID string `hash:"ignore"` // the id generated from the parent source struct
     6 + Name string
     7 + Metadata interface{}
     8 +}
     9 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/directory_source.go
     1 +package source
     2 + 
     3 +import (
     4 + "fmt"
     5 + "github.com/anchore/syft/syft/artifact"
     6 + "github.com/bmatcuk/doublestar/v4"
     7 + "github.com/opencontainers/go-digest"
     8 + "os"
     9 + "path/filepath"
     10 + "strings"
     11 + "sync"
     12 +)
     13 + 
     14 +var (
     15 + _ Source = (*DirectorySource)(nil)
     16 + _ PathInterpreter = (*DirectorySource)(nil)
     17 +)
     18 + 
     19 +type DirectoryConfig struct {
     20 + Path string
     21 + Base string
     22 + Exclude ExcludeConfig
     23 + Name string // ? can this be done differently?
     24 +}
     25 + 
     26 +// TODO: add fs.FS support
     27 +type DirectorySource struct {
     28 + id artifact.ID
     29 + config DirectoryConfig
     30 + resolver *directoryResolver
     31 + mutex *sync.Mutex
     32 + 
     33 + // implements PathInterpreter
     34 +}
     35 + 
     36 +func NewFromDirectory(cfg DirectoryConfig) (Source, error) {
     37 + fi, err := os.Stat(cfg.Path)
     38 + if err != nil {
     39 + return nil, fmt.Errorf("unable to stat path=%q: %w", cfg.Path, err)
     40 + }
     41 + 
     42 + if !fi.IsDir() {
     43 + return nil, fmt.Errorf("given path is not a directory (path=%q): %w", cfg.Path, err)
     44 + }
     45 + 
     46 + return &DirectorySource{
     47 + id: artifact.ID(strings.TrimPrefix(digest.FromString(cfg.Path).String(), "sha256:")),
     48 + config: cfg,
     49 + mutex: &sync.Mutex{},
     50 + }, nil
     51 +}
     52 + 
     53 +func (s DirectorySource) ID() artifact.ID {
     54 + return s.id
     55 +}
     56 + 
     57 +func (s DirectorySource) Metadata() PathMetadata {
     58 + return PathMetadata{
     59 + Path: s.config.Path,
     60 + Base: s.config.Base,
     61 + }
     62 +}
     63 + 
     64 +func (s DirectorySource) Describe() Description {
     65 + return Description{
     66 + ID: string(s.id),
     67 + Name: s.config.Path,
     68 + Metadata: s.Metadata(),
     69 + }
     70 +}
     71 + 
     72 +func (s *DirectorySource) FileResolver(scope Scope) (FileResolver, error) {
     73 + s.mutex.Lock()
     74 + defer s.mutex.Unlock()
     75 + 
     76 + if s.resolver == nil {
     77 + exclusionFunctions, err := getDirectoryExclusionFunctions(s.config.Path, s.config.Exclude.Paths)
     78 + if err != nil {
     79 + return nil, err
     80 + }
     81 + 
     82 + resolver, err := newDirectoryResolver(s.config.Path, s.config.Base, exclusionFunctions...)
     83 + if err != nil {
     84 + return nil, fmt.Errorf("unable to create directory resolver: %w", err)
     85 + }
     86 + 
     87 + s.resolver = resolver
     88 + }
     89 + 
     90 + return s.resolver, nil
     91 +}
     92 + 
     93 +func (s *DirectorySource) Close() error {
     94 + s.mutex.Lock()
     95 + defer s.mutex.Unlock()
     96 + s.resolver = nil
     97 + return nil
     98 +}
     99 + 
     100 +func getDirectoryExclusionFunctions(root string, exclusions []string) ([]pathIndexVisitor, error) {
     101 + if len(exclusions) == 0 {
     102 + return nil, nil
     103 + }
     104 + 
     105 + // this is what directoryResolver.indexTree is doing to get the absolute path:
     106 + root, err := filepath.Abs(root)
     107 + if err != nil {
     108 + return nil, err
     109 + }
     110 + 
     111 + // this handles Windows file paths by converting them to C:/something/else format
     112 + root = filepath.ToSlash(root)
     113 + 
     114 + if !strings.HasSuffix(root, "/") {
     115 + root += "/"
     116 + }
     117 + 
     118 + var errors []string
     119 + for idx, exclusion := range exclusions {
     120 + // check exclusions for supported paths, these are all relative to the "scan root"
     121 + if strings.HasPrefix(exclusion, "./") || strings.HasPrefix(exclusion, "*/") || strings.HasPrefix(exclusion, "**/") {
     122 + exclusion = strings.TrimPrefix(exclusion, "./")
     123 + exclusions[idx] = root + exclusion
     124 + } else {
     125 + errors = append(errors, exclusion)
     126 + }
     127 + }
     128 + 
     129 + if errors != nil {
     130 + return nil, fmt.Errorf("invalid exclusion pattern(s): '%s' (must start with one of: './', '*/', or '**/')", strings.Join(errors, "', '"))
     131 + }
     132 + 
     133 + return []pathIndexVisitor{
     134 + func(path string, info os.FileInfo, _ error) error {
     135 + for _, exclusion := range exclusions {
     136 + // this is required to handle Windows filepaths
     137 + path = filepath.ToSlash(path)
     138 + matches, err := doublestar.Match(exclusion, path)
     139 + if err != nil {
     140 + return nil
     141 + }
     142 + if matches {
     143 + if info != nil && info.IsDir() {
     144 + return filepath.SkipDir
     145 + }
     146 + return errSkipPath
     147 + }
     148 + }
     149 + return nil
     150 + },
     151 + }, nil
     152 +}
     153 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/file_source.go
     1 +package source
     2 + 
     3 +import (
     4 + "fmt"
     5 + "github.com/anchore/syft/internal/log"
     6 + "github.com/anchore/syft/syft/artifact"
     7 + "github.com/mholt/archiver/v3"
     8 + "github.com/opencontainers/go-digest"
     9 + "os"
     10 + "strings"
     11 + "sync"
     12 +)
     13 + 
     14 +var (
     15 + _ Source = (*FileSource)(nil)
     16 + _ PathInterpreter = (*FileSource)(nil)
     17 +)
     18 + 
     19 +type FileConfig struct {
     20 + Path string
     21 + Exclude ExcludeConfig
     22 + Name string // ? can this be done differently?
     23 + // base??
     24 +}
     25 + 
     26 +// TODO: add fs.FS support
     27 +type FileSource struct {
     28 + id artifact.ID
     29 + config FileConfig
     30 + resolver *directoryResolver
     31 + mutex *sync.Mutex
     32 + closer func() error
     33 + analysisPath string
     34 +}
     35 + 
     36 +func NewFromFile(cfg FileConfig) (Source, error) {
     37 + fileMeta, err := os.Stat(cfg.Path)
     38 + if err != nil {
     39 + return nil, fmt.Errorf("unable to stat path=%q: %w", cfg.Path, err)
     40 + }
     41 + 
     42 + if fileMeta.IsDir() {
     43 + return nil, fmt.Errorf("given path is a directory (path=%q): %w", cfg.Path, err)
     44 + }
     45 + 
     46 + analysisPath, cleanupFn := fileAnalysisPath(cfg.Path)
     47 + 
     48 + return &FileSource{
     49 + id: artifact.ID(strings.TrimPrefix(digestOfFileContents(cfg.Path), "sha256:")),
     50 + config: FileConfig{},
     51 + mutex: &sync.Mutex{},
     52 + closer: cleanupFn,
     53 + analysisPath: analysisPath,
     54 + }, nil
     55 +}
     56 + 
     57 +func (s FileSource) ID() artifact.ID {
     58 + return s.id
     59 +}
     60 + 
     61 +func (s FileSource) Metadata() PathMetadata {
     62 + return PathMetadata{
     63 + Path: s.config.Path,
     64 + Base: "",
     65 + }
     66 +}
     67 + 
     68 +func (s FileSource) Describe() Description {
     69 + return Description{
     70 + ID: string(s.id),
     71 + Name: s.config.Path,
     72 + Metadata: s.Metadata(),
     73 + }
     74 +}
     75 + 
     76 +func (s FileSource) FileResolver(scope Scope) (FileResolver, error) {
     77 + s.mutex.Lock()
     78 + defer s.mutex.Unlock()
     79 + 
     80 + if s.resolver == nil {
     81 + exclusionFunctions, err := getDirectoryExclusionFunctions(s.analysisPath, s.config.Exclude.Paths)
     82 + if err != nil {
     83 + return nil, err
     84 + }
     85 + 
     86 + resolver, err := newDirectoryResolver(s.analysisPath, "", exclusionFunctions...)
     87 + if err != nil {
     88 + return nil, fmt.Errorf("unable to create directory resolver: %w", err)
     89 + }
     90 + 
     91 + s.resolver = resolver
     92 + }
     93 + 
     94 + return s.resolver, nil
     95 +}
     96 + 
     97 +func (s FileSource) Close() error {
     98 + if s.closer == nil {
     99 + return nil
     100 + }
     101 + s.resolver = nil
     102 + return s.closer()
     103 +}
     104 + 
     105 +// fileAnalysisPath returns the path given, or in the case the path is an archive, the location where the archive
     106 +// contents have been made available. A cleanup function is provided for any temp files created (if any).
     107 +func fileAnalysisPath(path string) (string, func() error) {
     108 + var analysisPath = path
     109 + var cleanupFn = func() error { return nil }
     110 + 
     111 + // if the given file is an archive (as indicated by the file extension and not MIME type) then unarchive it and
     112 + // use the contents as the source. Note: this does NOT recursively unarchive contents, only the given path is
     113 + // unarchived.
     114 + envelopedUnarchiver, err := archiver.ByExtension(path)
     115 + if unarchiver, ok := envelopedUnarchiver.(archiver.Unarchiver); err == nil && ok {
     116 + if tar, ok := unarchiver.(*archiver.Tar); ok {
     117 + // when tar files are extracted, if there are multiple entries at the same
     118 + // location, the last entry wins
     119 + // NOTE: this currently does not display any messages if an overwrite happens
     120 + tar.OverwriteExisting = true
     121 + }
     122 + unarchivedPath, tmpCleanup, err := unarchiveToTmp(path, unarchiver)
     123 + if err != nil {
     124 + log.Warnf("file could not be unarchived: %+v", err)
     125 + } else {
     126 + log.Debugf("source path is an archive")
     127 + analysisPath = unarchivedPath
     128 + }
     129 + if tmpCleanup != nil {
     130 + cleanupFn = tmpCleanup
     131 + }
     132 + }
     133 + 
     134 + return analysisPath, cleanupFn
     135 +}
     136 + 
     137 +func digestOfFileContents(path string) string {
     138 + file, err := os.Open(path)
     139 + if err != nil {
     140 + return digest.FromString(path).String()
     141 + }
     142 + defer file.Close()
     143 + di, err := digest.FromReader(file)
     144 + if err != nil {
     145 + return digest.FromString(path).String()
     146 + }
     147 + return di.String()
     148 +}
     149 + 
     150 +func unarchiveToTmp(path string, unarchiver archiver.Unarchiver) (string, func() error, error) {
     151 + tempDir, err := os.MkdirTemp("", "syft-archive-contents-")
     152 + if err != nil {
     153 + return "", func() error { return nil }, fmt.Errorf("unable to create tempdir for archive processing: %w", err)
     154 + }
     155 + 
     156 + cleanupFn := func() error {
     157 + return os.RemoveAll(tempDir)
     158 + }
     159 + 
     160 + return tempDir, cleanupFn, unarchiver.Unarchive(path, tempDir) // TODO: does not work, use v4 for io.FS support
     161 +}
     162 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/image_source.go
     1 +package source
     2 + 
     3 +import (
     4 + "context"
     5 + "fmt"
     6 + "github.com/anchore/stereoscope"
     7 + "github.com/anchore/stereoscope/pkg/image"
     8 + "github.com/anchore/syft/syft/artifact"
     9 + "github.com/bmatcuk/doublestar/v4"
     10 + "github.com/opencontainers/go-digest"
     11 + "strings"
     12 +)
     13 + 
     14 +var (
     15 + _ Source = (*StereoscopeImageSource)(nil)
     16 + _ ImageInterpreter = (*StereoscopeImageSource)(nil)
     17 +)
     18 + 
     19 +type StereoscopeImageConfig struct {
     20 + Reference string
     21 + From image.Source
     22 + Platform *image.Platform
     23 + RegistryOptions *image.RegistryOptions // TODO: takes platform? as string?
     24 + Exclude ExcludeConfig
     25 + Name string // ? can this be done differently?
     26 +}
     27 + 
     28 +type StereoscopeImageSource struct {
     29 + id artifact.ID
     30 + config StereoscopeImageConfig
     31 + image *image.Image
     32 + metadata ImageMetadata
     33 +}
     34 + 
     35 +func NewFromImage(cfg StereoscopeImageConfig) (Source, error) {
     36 + ctx := context.TODO()
     37 + 
     38 + var opts []stereoscope.Option
     39 + if cfg.RegistryOptions != nil {
     40 + opts = append(opts, stereoscope.WithRegistryOptions(*cfg.RegistryOptions))
     41 + }
     42 + 
     43 + if cfg.Platform != nil {
     44 + opts = append(opts, stereoscope.WithPlatform(cfg.Platform.String()))
     45 + }
     46 + 
     47 + img, err := stereoscope.GetImageFromSource(ctx, cfg.Reference, cfg.From, opts...)
     48 + if err != nil {
     49 + return nil, fmt.Errorf("unable to load image: %w", err)
     50 + }
     51 + 
     52 + metadata := imageMetadataFromStereoscopeImage(img, cfg.Reference)
     53 + 
     54 + return &StereoscopeImageSource{
     55 + id: artifactIDFromStereoscopeImage(metadata),
     56 + config: cfg,
     57 + image: img,
     58 + metadata: metadata,
     59 + }, nil
     60 +}
     61 + 
     62 +func (s StereoscopeImageSource) ID() artifact.ID {
     63 + return s.id
     64 +}
     65 + 
     66 +func (s StereoscopeImageSource) Metadata() ImageMetadata {
     67 + return s.metadata
     68 +}
     69 + 
     70 +func (s StereoscopeImageSource) Describe() Description {
     71 + return Description{
     72 + ID: string(s.id),
     73 + Name: s.config.Reference,
     74 + Metadata: s.Metadata(),
     75 + }
     76 +}
     77 + 
     78 +func (s StereoscopeImageSource) FileResolver(scope Scope) (FileResolver, error) {
     79 + var resolver FileResolver
     80 + var err error
     81 + 
     82 + switch scope {
     83 + case SquashedScope:
     84 + resolver, err = newImageSquashResolver(s.image)
     85 + case AllLayersScope:
     86 + resolver, err = newAllLayersResolver(s.image)
     87 + default:
     88 + return nil, fmt.Errorf("bad image scope provided: %+v", scope)
     89 + }
     90 + 
     91 + if err != nil {
     92 + return nil, err
     93 + }
     94 + 
     95 + // image tree contains all paths, so we filter out the excluded entries afterward
     96 + if len(s.config.Exclude.Paths) > 0 {
     97 + resolver = NewExcludingResolver(resolver, getImageExclusionFunction(s.config.Exclude.Paths))
     98 + }
     99 + 
     100 + return resolver, nil
     101 +}
     102 + 
     103 +func (s StereoscopeImageSource) Close() error {
     104 + if s.image == nil {
     105 + return nil
     106 + }
     107 + return s.image.Cleanup()
     108 +}
     109 + 
     110 +func imageMetadataFromStereoscopeImage(img *image.Image, reference string) ImageMetadata {
     111 + 
     112 + tags := make([]string, len(img.Metadata.Tags))
     113 + for idx, tag := range img.Metadata.Tags {
     114 + tags[idx] = tag.String()
     115 + }
     116 + 
     117 + layers := make([]LayerMetadata, len(img.Layers))
     118 + for _, l := range img.Layers {
     119 + layers = append(layers,
     120 + LayerMetadata{
     121 + MediaType: string(l.Metadata.MediaType),
     122 + Digest: l.Metadata.Digest,
     123 + Size: l.Metadata.Size,
     124 + },
     125 + )
     126 + }
     127 + 
     128 + return ImageMetadata{
     129 + ID: img.Metadata.ID,
     130 + UserInput: reference,
     131 + ManifestDigest: img.Metadata.ManifestDigest,
     132 + Size: img.Metadata.Size,
     133 + MediaType: string(img.Metadata.MediaType),
     134 + Tags: tags,
     135 + Layers: layers,
     136 + RawConfig: img.Metadata.RawConfig,
     137 + RawManifest: img.Metadata.RawManifest,
     138 + RepoDigests: img.Metadata.RepoDigests,
     139 + Architecture: img.Metadata.Architecture,
     140 + Variant: img.Metadata.Variant,
     141 + OS: img.Metadata.OS,
     142 + }
     143 +}
     144 + 
     145 +func artifactIDFromStereoscopeImage(metadata ImageMetadata) artifact.ID {
     146 + var input string
     147 + 
     148 + manifestDigest := digest.FromBytes(metadata.RawManifest).String()
     149 + if manifestDigest != "" {
     150 + input = manifestDigest
     151 + } else {
     152 + // calculate chain ID for image sources where manifestDigest is not available
     153 + // https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
     154 + input = calculateChainID(metadata.Layers)
     155 + if input == "" {
     156 + // TODO what happens here if image has no layers?
     157 + // is this case possible?
     158 + input = digest.FromString(metadata.UserInput).String()
     159 + }
     160 + 
     161 + }
     162 + 
     163 + return artifact.ID(strings.TrimPrefix(input, "sha256:"))
     164 +}
     165 + 
     166 +func calculateChainID(lm []LayerMetadata) string {
     167 + if len(lm) < 1 {
     168 + return ""
     169 + }
     170 + 
     171 + // DiffID(L0) = digest of layer 0
     172 + // https://github.com/anchore/stereoscope/blob/1b1b744a919964f38d14e1416fb3f25221b761ce/pkg/image/layer_metadata.go#L19-L32
     173 + chainID := lm[0].Digest
     174 + id := chain(chainID, lm[1:])
     175 + 
     176 + return id
     177 +}
     178 + 
     179 +func chain(chainID string, layers []LayerMetadata) string {
     180 + if len(layers) < 1 {
     181 + return chainID
     182 + }
     183 + 
     184 + chainID = digest.FromString(layers[0].Digest + " " + chainID).String()
     185 + return chain(chainID, layers[1:])
     186 +}
     187 + 
     188 +func getImageExclusionFunction(exclusions []string) func(string) bool {
     189 + if len(exclusions) == 0 {
     190 + return nil
     191 + }
     192 + // add subpath exclusions
     193 + for _, exclusion := range exclusions {
     194 + exclusions = append(exclusions, exclusion+"/**")
     195 + }
     196 + return func(path string) bool {
     197 + for _, exclusion := range exclusions {
     198 + matches, err := doublestar.Match(exclusion, path)
     199 + if err != nil {
     200 + return false
     201 + }
     202 + if matches {
     203 + return true
     204 + }
     205 + }
     206 + return false
     207 + }
     208 +}
     209 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/metadata.go
    1  -package source
    2  - 
    3  -// Metadata represents any static source data that helps describe "what" was cataloged.
    4  -type Metadata struct {
    5  - ID string `hash:"ignore"` // the id generated from the parent source struct
    6  - Scheme Scheme // the source data scheme type (directory or image)
    7  - ImageMetadata ImageMetadata // all image info (image only)
    8  - Path string // the root path to be cataloged (directory only)
    9  - Base string // the base path to be cataloged (directory only)
    10  - Name string
    11  -}
    12  - 
  • ■ ■ ■ ■ ■ ■
    syft/source/scheme/scheme.go
     1 +package scheme
     2 + 
     3 +import (
     4 + "fmt"
     5 + "strings"
     6 + 
     7 + "github.com/mitchellh/go-homedir"
     8 + "github.com/spf13/afero"
     9 + 
     10 + "github.com/anchore/stereoscope/pkg/image"
     11 +)
     12 + 
     13 +// Scheme represents the optional prefixed string at the beginning of a user request (e.g. "docker:").
     14 +type Scheme string
     15 + 
     16 +type sourceDetector func(string) (image.Source, string, error)
     17 + 
     18 +const (
     19 + // UnknownScheme is the default scheme
     20 + UnknownScheme Scheme = "UnknownScheme"
     21 + // DirectoryScheme indicates the source being cataloged is a directory on the root filesystem
     22 + DirectoryScheme Scheme = "DirectoryScheme"
     23 + // ContainerImageScheme indicates the source being cataloged is a container image
     24 + ContainerImageScheme Scheme = "ContainerImageScheme"
     25 + // FileScheme indicates the source being cataloged is a single file
     26 + FileScheme Scheme = "FileScheme"
     27 +)
     28 + 
     29 +var AllSchemes = []Scheme{
     30 + DirectoryScheme,
     31 + ContainerImageScheme,
     32 + FileScheme,
     33 +}
     34 + 
     35 +// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
     36 +// It acts as a struct input for some source constructors.
     37 +type Input struct {
     38 + UserInput string
     39 + Scheme Scheme
     40 + ImageSource image.Source
     41 + Location string
     42 + Platform string
     43 + Name string
     44 +}
     45 + 
     46 +// Parse generates a source Input that can be used as an argument to generate a new source
     47 +// from specific providers including a registry, with an explicit name.
     48 +func Parse(userInput string, platform, name, defaultImageSource string) (*Input, error) {
     49 + fs := afero.NewOsFs()
     50 + scheme, source, location, err := detect(fs, image.DetectSource, userInput)
     51 + if err != nil {
     52 + return nil, err
     53 + }
     54 + 
     55 + if source == image.UnknownSource {
     56 + // only run for these two scheme
     57 + // only check on packages command, attest we automatically try to pull from userInput
     58 + switch scheme {
     59 + case ContainerImageScheme, UnknownScheme:
     60 + scheme = ContainerImageScheme
     61 + location = userInput
     62 + if defaultImageSource != "" {
     63 + source = parseDefaultImageSource(defaultImageSource)
     64 + } else {
     65 + source = image.DetermineDefaultImagePullSource(userInput)
     66 + }
     67 + default:
     68 + }
     69 + }
     70 + 
     71 + if scheme != ContainerImageScheme && platform != "" {
     72 + return nil, fmt.Errorf("cannot specify a platform for a non-image source")
     73 + }
     74 + 
     75 + // collect user input for downstream consumption
     76 + return &Input{
     77 + UserInput: userInput,
     78 + Scheme: scheme,
     79 + ImageSource: source,
     80 + Location: location,
     81 + Platform: platform,
     82 + Name: name,
     83 + }, nil
     84 +}
     85 + 
     86 +func detect(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, image.Source, string, error) {
     87 + switch {
     88 + case strings.HasPrefix(userInput, "dir:"):
     89 + dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
     90 + if err != nil {
     91 + return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
     92 + }
     93 + return DirectoryScheme, image.UnknownSource, dirLocation, nil
     94 + 
     95 + case strings.HasPrefix(userInput, "file:"):
     96 + fileLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "file:"))
     97 + if err != nil {
     98 + return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
     99 + }
     100 + return FileScheme, image.UnknownSource, fileLocation, nil
     101 + }
     102 + 
     103 + // try the most specific sources first and move out towards more generic sources.
     104 + 
     105 + // first: let's try the image detector, which has more scheme parsing internal to stereoscope
     106 + source, imageSpec, err := imageDetector(userInput)
     107 + if err == nil && source != image.UnknownSource {
     108 + return ContainerImageScheme, source, imageSpec, nil
     109 + }
     110 + 
     111 + // next: let's try more generic sources (dir, file, etc.)
     112 + location, err := homedir.Expand(userInput)
     113 + if err != nil {
     114 + return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand potential directory path: %w", err)
     115 + }
     116 + 
     117 + fileMeta, err := fs.Stat(location)
     118 + if err != nil {
     119 + return UnknownScheme, source, "", nil
     120 + }
     121 + 
     122 + if fileMeta.IsDir() {
     123 + return DirectoryScheme, source, location, nil
     124 + }
     125 + 
     126 + return FileScheme, source, location, nil
     127 +}
     128 + 
     129 +func parseDefaultImageSource(defaultImageSource string) image.Source {
     130 + switch defaultImageSource {
     131 + case "registry":
     132 + return image.OciRegistrySource
     133 + case "docker":
     134 + return image.DockerDaemonSource
     135 + case "podman":
     136 + return image.PodmanDaemonSource
     137 + default:
     138 + return image.UnknownSource
     139 + }
     140 +}
     141 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/scheme_test.go syft/source/scheme/scheme_test.go
    1  -package source
     1 +package scheme
    2 2   
    3 3  import (
    4 4   "os"
    skipped 29 lines
    34 34   src: image.DockerDaemonSource,
    35 35   ref: "wagoodman/dive:latest",
    36 36   },
    37  - expectedScheme: ImageScheme,
     37 + expectedScheme: ContainerImageScheme,
    38 38   expectedLocation: "wagoodman/dive:latest",
    39 39   },
    40 40   {
    skipped 3 lines
    44 44   src: image.DockerDaemonSource,
    45 45   ref: "wagoodman/dive",
    46 46   },
    47  - expectedScheme: ImageScheme,
     47 + expectedScheme: ContainerImageScheme,
    48 48   expectedLocation: "wagoodman/dive",
    49 49   },
    50 50   {
    skipped 3 lines
    54 54   src: image.OciRegistrySource,
    55 55   ref: "wagoodman/dive:latest",
    56 56   },
    57  - expectedScheme: ImageScheme,
     57 + expectedScheme: ContainerImageScheme,
    58 58   expectedLocation: "wagoodman/dive:latest",
    59 59   },
    60 60   {
    skipped 3 lines
    64 64   src: image.DockerDaemonSource,
    65 65   ref: "wagoodman/dive:latest",
    66 66   },
    67  - expectedScheme: ImageScheme,
     67 + expectedScheme: ContainerImageScheme,
    68 68   expectedLocation: "wagoodman/dive:latest",
    69 69   },
    70 70   {
    skipped 3 lines
    74 74   src: image.DockerDaemonSource,
    75 75   ref: "wagoodman/dive",
    76 76   },
    77  - expectedScheme: ImageScheme,
     77 + expectedScheme: ContainerImageScheme,
    78 78   expectedLocation: "wagoodman/dive",
    79 79   },
    80 80   {
    skipped 3 lines
    84 84   src: image.DockerDaemonSource,
    85 85   ref: "latest",
    86 86   },
    87  - expectedScheme: ImageScheme,
     87 + expectedScheme: ContainerImageScheme,
    88 88   // we expected to be able to handle this case better, however, I don't see a way to do this
    89 89   // the user will need to provide more explicit input (docker:docker:latest)
    90 90   expectedLocation: "latest",
    skipped 5 lines
    96 96   src: image.DockerDaemonSource,
    97 97   ref: "docker:latest",
    98 98   },
    99  - expectedScheme: ImageScheme,
     99 + expectedScheme: ContainerImageScheme,
    100 100   // we expected to be able to handle this case better, however, I don't see a way to do this
    101 101   // the user will need to provide more explicit input (docker:docker:latest)
    102 102   expectedLocation: "docker:latest",
    skipped 5 lines
    108 108   src: image.OciTarballSource,
    109 109   ref: "some/path-to-file",
    110 110   },
    111  - expectedScheme: ImageScheme,
     111 + expectedScheme: ContainerImageScheme,
    112 112   expectedLocation: "some/path-to-file",
    113 113   },
    114 114   {
    skipped 4 lines
    119 119   ref: "some/path-to-dir",
    120 120   },
    121 121   dirs: []string{"some/path-to-dir"},
    122  - expectedScheme: ImageScheme,
     122 + expectedScheme: ContainerImageScheme,
    123 123   expectedLocation: "some/path-to-dir",
    124 124   },
    125 125   {
    skipped 14 lines
    140 140   src: image.DockerDaemonSource,
    141 141   ref: "some/path-to-dir",
    142 142   },
    143  - expectedScheme: ImageScheme,
     143 + expectedScheme: ContainerImageScheme,
    144 144   expectedLocation: "some/path-to-dir",
    145 145   },
    146 146   {
    skipped 3 lines
    150 150   src: image.PodmanDaemonSource,
    151 151   ref: "something:latest",
    152 152   },
    153  - expectedScheme: ImageScheme,
     153 + expectedScheme: ContainerImageScheme,
    154 154   expectedLocation: "something:latest",
    155 155   },
    156 156   {
    skipped 57 lines
    214 214   src: image.OciDirectorySource,
    215 215   ref: "~/some-path",
    216 216   },
    217  - expectedScheme: ImageScheme,
     217 + expectedScheme: ContainerImageScheme,
    218 218   expectedLocation: "~/some-path",
    219 219   },
    220 220   {
    skipped 67 lines
    288 288   }
    289 289   }
    290 290   
    291  - actualScheme, actualSource, actualLocation, err := DetectScheme(fs, imageDetector, test.userInput)
     291 + actualScheme, actualSource, actualLocation, err := detect(fs, imageDetector, test.userInput)
    292 292   if err != nil {
    293 293   t.Fatalf("unexpected err : %+v", err)
    294 294   }
    skipped 15 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/scheme/source.go
     1 +package scheme
     2 + 
     3 +import (
     4 + "fmt"
     5 + "github.com/anchore/stereoscope/pkg/image"
     6 + "github.com/anchore/syft/syft/source"
     7 +)
     8 + 
     9 +// NewSource produces a Source based on userInput like dir: or image:tag
     10 +func NewSource(in Input, registryOptions *image.RegistryOptions, exclusions []string) (source.Source, error) {
     11 + var err error
     12 + var src source.Source
     13 + 
     14 + switch in.Scheme {
     15 + case FileScheme:
     16 + src, err = source.NewFromFile(
     17 + source.FileConfig{
     18 + Path: in.Location,
     19 + Exclude: source.ExcludeConfig{
     20 + Paths: exclusions,
     21 + },
     22 + Name: in.Name,
     23 + },
     24 + )
     25 + case DirectoryScheme:
     26 + src, err = source.NewFromDirectory(
     27 + source.DirectoryConfig{
     28 + Path: in.Location,
     29 + Base: in.Location,
     30 + Exclude: source.ExcludeConfig{},
     31 + Name: in.Name,
     32 + },
     33 + )
     34 + case ContainerImageScheme:
     35 + var platform *image.Platform
     36 + if in.Platform != "" {
     37 + platform, err = image.NewPlatform(in.Platform)
     38 + if err != nil {
     39 + return nil, fmt.Errorf("unable to parse platform: %w", err)
     40 + }
     41 + }
     42 + src, err = source.NewFromImage(
     43 + source.StereoscopeImageConfig{
     44 + Reference: in.Location,
     45 + From: in.ImageSource,
     46 + Platform: platform,
     47 + RegistryOptions: registryOptions,
     48 + Exclude: source.ExcludeConfig{
     49 + Paths: exclusions,
     50 + },
     51 + Name: in.Name,
     52 + },
     53 + )
     54 + default:
     55 + err = fmt.Errorf("unable to process input for scanning: %q", in.UserInput)
     56 + }
     57 + 
     58 + return src, err
     59 +}
     60 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/scheme.go
    1  -package source
    2  - 
    3  -import (
    4  - "fmt"
    5  - "strings"
    6  - 
    7  - "github.com/mitchellh/go-homedir"
    8  - "github.com/spf13/afero"
    9  - 
    10  - "github.com/anchore/stereoscope/pkg/image"
    11  -)
    12  - 
    13  -// Scheme represents the optional prefixed string at the beginning of a user request (e.g. "docker:").
    14  -type Scheme string
    15  - 
    16  -const (
    17  - // UnknownScheme is the default scheme
    18  - UnknownScheme Scheme = "UnknownScheme"
    19  - // DirectoryScheme indicates the source being cataloged is a directory on the root filesystem
    20  - DirectoryScheme Scheme = "DirectoryScheme"
    21  - // ImageScheme indicates the source being cataloged is a container image
    22  - ImageScheme Scheme = "ImageScheme"
    23  - // FileScheme indicates the source being cataloged is a single file
    24  - FileScheme Scheme = "FileScheme"
    25  -)
    26  - 
    27  -var AllSchemes = []Scheme{
    28  - DirectoryScheme,
    29  - ImageScheme,
    30  - FileScheme,
    31  -}
    32  - 
    33  -func DetectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, image.Source, string, error) {
    34  - switch {
    35  - case strings.HasPrefix(userInput, "dir:"):
    36  - dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
    37  - if err != nil {
    38  - return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
    39  - }
    40  - return DirectoryScheme, image.UnknownSource, dirLocation, nil
    41  - 
    42  - case strings.HasPrefix(userInput, "file:"):
    43  - fileLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "file:"))
    44  - if err != nil {
    45  - return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand directory path: %w", err)
    46  - }
    47  - return FileScheme, image.UnknownSource, fileLocation, nil
    48  - }
    49  - 
    50  - // try the most specific sources first and move out towards more generic sources.
    51  - 
    52  - // first: let's try the image detector, which has more scheme parsing internal to stereoscope
    53  - source, imageSpec, err := imageDetector(userInput)
    54  - if err == nil && source != image.UnknownSource {
    55  - return ImageScheme, source, imageSpec, nil
    56  - }
    57  - 
    58  - // next: let's try more generic sources (dir, file, etc.)
    59  - location, err := homedir.Expand(userInput)
    60  - if err != nil {
    61  - return UnknownScheme, image.UnknownSource, "", fmt.Errorf("unable to expand potential directory path: %w", err)
    62  - }
    63  - 
    64  - fileMeta, err := fs.Stat(location)
    65  - if err != nil {
    66  - return UnknownScheme, source, "", nil
    67  - }
    68  - 
    69  - if fileMeta.IsDir() {
    70  - return DirectoryScheme, source, location, nil
    71  - }
    72  - 
    73  - return FileScheme, source, location, nil
    74  -}
    75  - 
  • ■ ■ ■ ■
    syft/source/scope.go
    skipped 9 lines
    10 10   UnknownScope Scope = "UnknownScope"
    11 11   // SquashedScope indicates to only catalog content visible from the squashed filesystem representation (what can be seen only within the container at runtime)
    12 12   SquashedScope Scope = "Squashed"
    13  - // AllLayersScope indicates to catalog content on all layers, irregardless if it is visible from the container at runtime.
     13 + // AllLayersScope indicates to catalog content on all layers, regardless if it is visible from the container at runtime.
    14 14   AllLayersScope Scope = "AllLayers"
    15 15  )
    16 16   
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/source.go
    skipped 5 lines
    6 6  package source
    7 7   
    8 8  import (
    9  - "context"
    10  - "fmt"
    11  - "os"
    12  - "path/filepath"
    13  - "strings"
    14  - "sync"
    15  - 
    16  - "github.com/bmatcuk/doublestar/v4"
    17  - "github.com/mholt/archiver/v3"
    18  - digest "github.com/opencontainers/go-digest"
    19  - "github.com/spf13/afero"
    20  - 
    21  - "github.com/anchore/stereoscope"
    22  - "github.com/anchore/stereoscope/pkg/image"
    23  - "github.com/anchore/syft/internal/log"
    24 9   "github.com/anchore/syft/syft/artifact"
     10 + "io"
     11 + "strings"
    25 12  )
    26 13   
    27  -// Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used
    28  -// in cataloging (based on the data source and configuration)
    29  -type Source struct {
    30  - id artifact.ID `hash:"ignore"`
    31  - Image *image.Image `hash:"ignore"` // the image object to be cataloged (image only)
    32  - Metadata Metadata
    33  - directoryResolver *directoryResolver `hash:"ignore"`
    34  - path string
    35  - base string
    36  - mutex *sync.Mutex
    37  - Exclusions []string `hash:"ignore"`
     14 +type Source interface {
     15 + artifact.Identifiable
     16 + FileResolver(Scope) (FileResolver, error)
     17 + Describe() Description
     18 + io.Closer
    38 19  }
    39 20   
    40  -// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
    41  -// It acts as a struct input for some source constructors.
    42  -type Input struct {
    43  - UserInput string
    44  - Scheme Scheme
    45  - ImageSource image.Source
    46  - Location string
    47  - Platform string
    48  - Name string
    49  -}
     21 +//// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
     22 +//// It acts as a struct input for some source constructors.
     23 +//type Input struct {
     24 +// UserInput string
     25 +// Scheme Scheme
     26 +// ImageSource image.Source
     27 +// Location string
     28 +// Platform string
     29 +// Name string
     30 +//}
     31 +//
     32 +//// ParseInput generates a source Input that can be used as an argument to generate a new source
     33 +//// from specific providers including a registry.
     34 +//func ParseInput(userInput string, platform string) (*Input, error) {
     35 +// return ParseInputWithName(userInput, platform, "", "")
     36 +//}
     37 +//
     38 +//// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
     39 +//// from specific providers including a registry, with an explicit name.
     40 +//func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) {
     41 +// fs := afero.NewOsFs()
     42 +// scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
     43 +// if err != nil {
     44 +// return nil, err
     45 +// }
     46 +//
     47 +// if source == image.UnknownSource {
     48 +// // only run for these two scheme
     49 +// // only check on packages command, attest we automatically try to pull from userInput
     50 +// switch scheme {
     51 +// case ImageScheme, UnknownScheme:
     52 +// scheme = ImageScheme
     53 +// location = userInput
     54 +// if defaultImageSource != "" {
     55 +// source = parseDefaultImageSource(defaultImageSource)
     56 +// } else {
     57 +// imagePullSource := image.DetermineDefaultImagePullSource(userInput)
     58 +// source = imagePullSource
     59 +// }
     60 +// if location == "" {
     61 +// location = userInput
     62 +// }
     63 +// default:
     64 +// }
     65 +// }
     66 +//
     67 +// if scheme != ImageScheme && platform != "" {
     68 +// return nil, fmt.Errorf("cannot specify a platform for a non-image source")
     69 +// }
     70 +//
     71 +// // collect user input for downstream consumption
     72 +// return &Input{
     73 +// UserInput: userInput,
     74 +// Scheme: scheme,
     75 +// ImageSource: source,
     76 +// Location: location,
     77 +// Platform: platform,
     78 +// Name: name,
     79 +// }, nil
     80 +//}
     81 +//
     82 +//func parseDefaultImageSource(defaultImageSource string) image.Source {
     83 +// switch defaultImageSource {
     84 +// case "registry":
     85 +// return image.OciRegistrySource
     86 +// case "docker":
     87 +// return image.DockerDaemonSource
     88 +// case "podman":
     89 +// return image.PodmanDaemonSource
     90 +// default:
     91 +// return image.UnknownSource
     92 +// }
     93 +//}
     94 +//
     95 +//type sourceDetector func(string) (image.Source, string, error)
    50 96   
    51  -// ParseInput generates a source Input that can be used as an argument to generate a new source
    52  -// from specific providers including a registry.
    53  -func ParseInput(userInput string, platform string) (*Input, error) {
    54  - return ParseInputWithName(userInput, platform, "", "")
    55  -}
     97 +//func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
     98 +// source, cleanupFn, err := generateImageSource(in, registryOptions)
     99 +// if source != nil {
     100 +// source.Exclusions = exclusions
     101 +// }
     102 +// return source, cleanupFn, err
     103 +//}
     104 +//
     105 +//// New produces a Source based on userInput like dir: or image:tag
     106 +//func New(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
     107 +// var err error
     108 +// fs := afero.NewOsFs()
     109 +// var source *Source
     110 +// cleanupFn := func() {}
     111 +//
     112 +// switch in.Scheme {
     113 +// case FileScheme:
     114 +// source, cleanupFn, err = generateFileSource(fs, in)
     115 +// case DirectoryScheme:
     116 +// source, cleanupFn, err = generateDirectorySource(fs, in)
     117 +// case ImageScheme:
     118 +// source, cleanupFn, err = generateImageSource(in, registryOptions)
     119 +// default:
     120 +// err = fmt.Errorf("unable to process input for scanning: %q", in.UserInput)
     121 +// }
     122 +//
     123 +// if err == nil {
     124 +// source.Exclusions = exclusions
     125 +// }
     126 +//
     127 +// return source, cleanupFn, err
     128 +//}
    56 129   
    57  -// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
    58  -// from specific providers including a registry, with an explicit name.
    59  -func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) {
    60  - fs := afero.NewOsFs()
    61  - scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
    62  - if err != nil {
    63  - return nil, err
    64  - }
    65  - 
    66  - if source == image.UnknownSource {
    67  - // only run for these two scheme
    68  - // only check on packages command, attest we automatically try to pull from userInput
    69  - switch scheme {
    70  - case ImageScheme, UnknownScheme:
    71  - scheme = ImageScheme
    72  - location = userInput
    73  - if defaultImageSource != "" {
    74  - source = parseDefaultImageSource(defaultImageSource)
    75  - } else {
    76  - imagePullSource := image.DetermineDefaultImagePullSource(userInput)
    77  - source = imagePullSource
    78  - }
    79  - if location == "" {
    80  - location = userInput
    81  - }
    82  - default:
    83  - }
    84  - }
    85  - 
    86  - if scheme != ImageScheme && platform != "" {
    87  - return nil, fmt.Errorf("cannot specify a platform for a non-image source")
    88  - }
    89  - 
    90  - // collect user input for downstream consumption
    91  - return &Input{
    92  - UserInput: userInput,
    93  - Scheme: scheme,
    94  - ImageSource: source,
    95  - Location: location,
    96  - Platform: platform,
    97  - Name: name,
    98  - }, nil
    99  -}
    100  - 
    101  -func parseDefaultImageSource(defaultImageSource string) image.Source {
    102  - switch defaultImageSource {
    103  - case "registry":
    104  - return image.OciRegistrySource
    105  - case "docker":
    106  - return image.DockerDaemonSource
    107  - case "podman":
    108  - return image.PodmanDaemonSource
    109  - default:
    110  - return image.UnknownSource
    111  - }
    112  -}
    113  - 
    114  -type sourceDetector func(string) (image.Source, string, error)
    115  - 
    116  -func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
    117  - source, cleanupFn, err := generateImageSource(in, registryOptions)
    118  - if source != nil {
    119  - source.Exclusions = exclusions
    120  - }
    121  - return source, cleanupFn, err
    122  -}
    123  - 
    124  -// New produces a Source based on userInput like dir: or image:tag
    125  -func New(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
    126  - var err error
    127  - fs := afero.NewOsFs()
    128  - var source *Source
    129  - cleanupFn := func() {}
    130  - 
    131  - switch in.Scheme {
    132  - case FileScheme:
    133  - source, cleanupFn, err = generateFileSource(fs, in)
    134  - case DirectoryScheme:
    135  - source, cleanupFn, err = generateDirectorySource(fs, in)
    136  - case ImageScheme:
    137  - source, cleanupFn, err = generateImageSource(in, registryOptions)
    138  - default:
    139  - err = fmt.Errorf("unable to process input for scanning: %q", in.UserInput)
    140  - }
    141  - 
    142  - if err == nil {
    143  - source.Exclusions = exclusions
    144  - }
    145  - 
    146  - return source, cleanupFn, err
    147  -}
    148  - 
    149  -func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Source, func(), error) {
    150  - img, cleanup, err := getImageWithRetryStrategy(in, registryOptions)
    151  - if err != nil || img == nil {
    152  - return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err)
    153  - }
    154  - 
    155  - s, err := NewFromImageWithName(img, in.Location, in.Name)
    156  - if err != nil {
    157  - return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err)
    158  - }
    159  - 
    160  - return &s, cleanup, nil
    161  -}
     130 +//func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Source, func(), error) {
     131 +// img, cleanup, err := getImageWithRetryStrategy(in, registryOptions)
     132 +// if err != nil || img == nil {
     133 +// return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err)
     134 +// }
     135 +//
     136 +// s, err := NewFromImageWithName(img, in.Location, in.Name)
     137 +// if err != nil {
     138 +// return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err)
     139 +// }
     140 +//
     141 +// return &s, cleanup, nil
     142 +//}
    162 143   
    163 144  func parseScheme(userInput string) string {
    164 145   parts := strings.SplitN(userInput, ":", 2)
    skipped 4 lines
    169 150   return parts[0]
    170 151  }
    171 152   
    172  -func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) (*image.Image, func(), error) {
    173  - ctx := context.TODO()
     153 +//func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) {
     154 +// fileMeta, err := fs.Stat(in.Location)
     155 +// if err != nil {
     156 +// return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err)
     157 +// }
     158 +//
     159 +// if !fileMeta.IsDir() {
     160 +// return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err)
     161 +// }
     162 +//
     163 +// s, err := NewFromDirectoryWithName(in.Location, in.Name)
     164 +// if err != nil {
     165 +// return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", in.Location, err)
     166 +// }
     167 +//
     168 +// return &s, func() {}, nil
     169 +//}
    174 170   
    175  - var opts []stereoscope.Option
    176  - if registryOptions != nil {
    177  - opts = append(opts, stereoscope.WithRegistryOptions(*registryOptions))
    178  - }
     171 +//func generateFileSource(fs afero.Fs, in Input) (*Source, func(), error) {
     172 +// fileMeta, err := fs.Stat(in.Location)
     173 +// if err != nil {
     174 +// return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err)
     175 +// }
     176 +//
     177 +// if fileMeta.IsDir() {
     178 +// return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err)
     179 +// }
     180 +//
     181 +// s, cleanupFn := NewFromFileWithName(in.Location, in.Name)
     182 +//
     183 +// return &s, cleanupFn, nil
     184 +//}
    179 185   
    180  - if in.Platform != "" {
    181  - opts = append(opts, stereoscope.WithPlatform(in.Platform))
    182  - }
     186 +//// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
     187 +//func NewFromDirectory(path string) (Source, error) {
     188 +// return NewFromDirectoryWithName(path, "")
     189 +//}
     190 +//
     191 +//// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
     192 +//func NewFromDirectoryRoot(path string) (Source, error) {
     193 +// return NewFromDirectoryRootWithName(path, "")
     194 +//}
     195 +//
     196 +//// NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
     197 +//func NewFromDirectoryWithName(path string, name string) (Source, error) {
     198 +// s := Source{
     199 +// mutex: &sync.Mutex{},
     200 +// Metadata: Metadata{
     201 +// Name: name,
     202 +// Scheme: DirectoryScheme,
     203 +// Path: path,
     204 +// },
     205 +// path: path,
     206 +// }
     207 +// s.SetID()
     208 +// return s, nil
     209 +//}
     210 +//
     211 +//// NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
     212 +//func NewFromDirectoryRootWithName(path string, name string) (Source, error) {
     213 +// s := Source{
     214 +// mutex: &sync.Mutex{},
     215 +// Metadata: Metadata{
     216 +// Name: name,
     217 +// Scheme: DirectoryScheme,
     218 +// Path: path,
     219 +// Base: path,
     220 +// },
     221 +// path: path,
     222 +// base: path,
     223 +// }
     224 +// s.SetID()
     225 +// return s, nil
     226 +//}
    183 227   
    184  - img, err := stereoscope.GetImageFromSource(ctx, in.Location, in.ImageSource, opts...)
    185  - cleanup := func() {
    186  - if err := img.Cleanup(); err != nil {
    187  - log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err)
    188  - }
    189  - }
    190  - if err == nil {
    191  - // Success on the first try!
    192  - return img, cleanup, nil
    193  - }
     228 +//// NewFromFile creates a new source object tailored to catalog a file.
     229 +//func NewFromFile(path string) (Source, func()) {
     230 +// return NewFromFileWithName(path, "")
     231 +//}
     232 +//
     233 +//// NewFromFileWithName creates a new source object tailored to catalog a file, with an explicitly provided name.
     234 +//func NewFromFileWithName(path string, name string) (Source, func()) {
     235 +// analysisPath, cleanupFn := fileAnalysisPath(path)
     236 +//
     237 +// s := Source{
     238 +// mutex: &sync.Mutex{},
     239 +// Metadata: Metadata{
     240 +// Name: name,
     241 +// Scheme: FileScheme,
     242 +// Path: path,
     243 +// },
     244 +// path: analysisPath,
     245 +// }
     246 +//
     247 +// s.SetID()
     248 +// return s, cleanupFn
     249 +//}
    194 250   
    195  - scheme := parseScheme(in.UserInput)
    196  - if !(scheme == "docker" || scheme == "registry") {
    197  - // Image retrieval failed, and we shouldn't retry it. It's most likely that the
    198  - // user _did_ intend the parsed scheme, but there was a legitimate failure with
    199  - // using the scheme to load the image. Alert the user to this failure, so they
    200  - // can fix the problem.
    201  - return nil, nil, err
    202  - }
     251 +//
     252 +//// NewFromImage creates a new source object tailored to catalog a given container image, relative to the
     253 +//// option given (e.g. all-layers, squashed, etc)
     254 +//func NewFromImage(img *image.Image, userImageStr string) (Source, error) {
     255 +// return NewFromImageWithName(img, userImageStr, "")
     256 +//}
     257 +//
     258 +//// NewFromImageWithName creates a new source object tailored to catalog a given container image, relative to the
     259 +//// option given (e.g. all-layers, squashed, etc), with an explicit name.
     260 +//func NewFromImageWithName(img *image.Image, userImageStr string, name string) (Source, error) {
     261 +// if img == nil {
     262 +// return Source{}, fmt.Errorf("no image given")
     263 +// }
     264 +//
     265 +// s := Source{
     266 +// Image: img,
     267 +// Metadata: Metadata{
     268 +// Name: name,
     269 +// Scheme: ImageScheme,
     270 +// metadata: NewImageMetadata(img, userImageStr),
     271 +// },
     272 +// }
     273 +// s.SetID()
     274 +// return s, nil
     275 +//}
    203 276   
    204  - // Maybe the user wanted "docker" or "registry" to refer to an _image name_
    205  - // (e.g. "docker:latest"), not a scheme. We'll retry image retrieval with this
    206  - // alternative interpretation, in an attempt to avoid unnecessary user friction.
    207  - 
    208  - log.Warnf(
    209  - "scheme %q specified, but it coincides with a common image name; re-examining user input %q"+
    210  - " without scheme parsing because image retrieval using scheme parsing was unsuccessful: %v",
    211  - scheme,
    212  - in.UserInput,
    213  - err,
    214  - )
    215  - 
    216  - // We need to determine the image source again, such that this determination
    217  - // doesn't take scheme parsing into account.
    218  - in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
    219  - img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
    220  - cleanup = func() {
    221  - if err := img.Cleanup(); err != nil {
    222  - log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err)
    223  - }
    224  - }
    225  - return img, cleanup, err
    226  -}
    227  - 
    228  -func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) {
    229  - fileMeta, err := fs.Stat(in.Location)
    230  - if err != nil {
    231  - return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err)
    232  - }
    233  - 
    234  - if !fileMeta.IsDir() {
    235  - return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err)
    236  - }
    237  - 
    238  - s, err := NewFromDirectoryWithName(in.Location, in.Name)
    239  - if err != nil {
    240  - return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", in.Location, err)
    241  - }
    242  - 
    243  - return &s, func() {}, nil
    244  -}
    245  - 
    246  -func generateFileSource(fs afero.Fs, in Input) (*Source, func(), error) {
    247  - fileMeta, err := fs.Stat(in.Location)
    248  - if err != nil {
    249  - return nil, func() {}, fmt.Errorf("unable to stat dir=%q: %w", in.Location, err)
    250  - }
    251  - 
    252  - if fileMeta.IsDir() {
    253  - return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err)
    254  - }
    255  - 
    256  - s, cleanupFn := NewFromFileWithName(in.Location, in.Name)
    257  - 
    258  - return &s, cleanupFn, nil
    259  -}
    260  - 
    261  -// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
    262  -func NewFromDirectory(path string) (Source, error) {
    263  - return NewFromDirectoryWithName(path, "")
    264  -}
    265  - 
    266  -// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
    267  -func NewFromDirectoryRoot(path string) (Source, error) {
    268  - return NewFromDirectoryRootWithName(path, "")
    269  -}
    270  - 
    271  -// NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
    272  -func NewFromDirectoryWithName(path string, name string) (Source, error) {
    273  - s := Source{
    274  - mutex: &sync.Mutex{},
    275  - Metadata: Metadata{
    276  - Name: name,
    277  - Scheme: DirectoryScheme,
    278  - Path: path,
    279  - },
    280  - path: path,
    281  - }
    282  - s.SetID()
    283  - return s, nil
    284  -}
    285  - 
    286  -// NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
    287  -func NewFromDirectoryRootWithName(path string, name string) (Source, error) {
    288  - s := Source{
    289  - mutex: &sync.Mutex{},
    290  - Metadata: Metadata{
    291  - Name: name,
    292  - Scheme: DirectoryScheme,
    293  - Path: path,
    294  - Base: path,
    295  - },
    296  - path: path,
    297  - base: path,
    298  - }
    299  - s.SetID()
    300  - return s, nil
    301  -}
    302  - 
    303  -// NewFromFile creates a new source object tailored to catalog a file.
    304  -func NewFromFile(path string) (Source, func()) {
    305  - return NewFromFileWithName(path, "")
    306  -}
    307  - 
    308  -// NewFromFileWithName creates a new source object tailored to catalog a file, with an explicitly provided name.
    309  -func NewFromFileWithName(path string, name string) (Source, func()) {
    310  - analysisPath, cleanupFn := fileAnalysisPath(path)
    311  - 
    312  - s := Source{
    313  - mutex: &sync.Mutex{},
    314  - Metadata: Metadata{
    315  - Name: name,
    316  - Scheme: FileScheme,
    317  - Path: path,
    318  - },
    319  - path: analysisPath,
    320  - }
    321  - 
    322  - s.SetID()
    323  - return s, cleanupFn
    324  -}
    325  - 
    326  -// fileAnalysisPath returns the path given, or in the case the path is an archive, the location where the archive
    327  -// contents have been made available. A cleanup function is provided for any temp files created (if any).
    328  -func fileAnalysisPath(path string) (string, func()) {
    329  - var analysisPath = path
    330  - var cleanupFn = func() {}
    331  - 
    332  - // if the given file is an archive (as indicated by the file extension and not MIME type) then unarchive it and
    333  - // use the contents as the source. Note: this does NOT recursively unarchive contents, only the given path is
    334  - // unarchived.
    335  - envelopedUnarchiver, err := archiver.ByExtension(path)
    336  - if unarchiver, ok := envelopedUnarchiver.(archiver.Unarchiver); err == nil && ok {
    337  - if tar, ok := unarchiver.(*archiver.Tar); ok {
    338  - // when tar files are extracted, if there are multiple entries at the same
    339  - // location, the last entry wins
    340  - // NOTE: this currently does not display any messages if an overwrite happens
    341  - tar.OverwriteExisting = true
    342  - }
    343  - unarchivedPath, tmpCleanup, err := unarchiveToTmp(path, unarchiver)
    344  - if err != nil {
    345  - log.Warnf("file could not be unarchived: %+v", err)
    346  - } else {
    347  - log.Debugf("source path is an archive")
    348  - analysisPath = unarchivedPath
    349  - }
    350  - if tmpCleanup != nil {
    351  - cleanupFn = tmpCleanup
    352  - }
    353  - }
    354  - 
    355  - return analysisPath, cleanupFn
    356  -}
    357  - 
    358  -// NewFromImage creates a new source object tailored to catalog a given container image, relative to the
    359  -// option given (e.g. all-layers, squashed, etc)
    360  -func NewFromImage(img *image.Image, userImageStr string) (Source, error) {
    361  - return NewFromImageWithName(img, userImageStr, "")
    362  -}
    363  - 
    364  -// NewFromImageWithName creates a new source object tailored to catalog a given container image, relative to the
    365  -// option given (e.g. all-layers, squashed, etc), with an explicit name.
    366  -func NewFromImageWithName(img *image.Image, userImageStr string, name string) (Source, error) {
    367  - if img == nil {
    368  - return Source{}, fmt.Errorf("no image given")
    369  - }
    370  - 
    371  - s := Source{
    372  - Image: img,
    373  - Metadata: Metadata{
    374  - Name: name,
    375  - Scheme: ImageScheme,
    376  - ImageMetadata: NewImageMetadata(img, userImageStr),
    377  - },
    378  - }
    379  - s.SetID()
    380  - return s, nil
    381  -}
    382  - 
    383  -func (s *Source) ID() artifact.ID {
    384  - if s.id == "" {
    385  - s.SetID()
    386  - }
    387  - return s.id
    388  -}
    389  - 
    390  -func (s *Source) SetID() {
    391  - var d string
    392  - switch s.Metadata.Scheme {
    393  - case DirectoryScheme:
    394  - d = digest.FromString(s.Metadata.Path).String()
    395  - case FileScheme:
    396  - // attempt to use the digest of the contents of the file as the ID
    397  - file, err := os.Open(s.Metadata.Path)
    398  - if err != nil {
    399  - d = digest.FromString(s.Metadata.Path).String()
    400  - break
    401  - }
    402  - defer file.Close()
    403  - di, err := digest.FromReader(file)
    404  - if err != nil {
    405  - d = digest.FromString(s.Metadata.Path).String()
    406  - break
    407  - }
    408  - d = di.String()
    409  - case ImageScheme:
    410  - manifestDigest := digest.FromBytes(s.Metadata.ImageMetadata.RawManifest).String()
    411  - if manifestDigest != "" {
    412  - d = manifestDigest
    413  - break
    414  - }
    415  - 
    416  - // calcuate chain ID for image sources where manifestDigest is not available
    417  - // https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
    418  - d = calculateChainID(s.Metadata.ImageMetadata.Layers)
    419  - if d == "" {
    420  - // TODO what happens here if image has no layers?
    421  - // Is this case possible
    422  - d = digest.FromString(s.Metadata.ImageMetadata.UserInput).String()
    423  - }
    424  - default: // for UnknownScheme we hash the struct
    425  - id, _ := artifact.IDByHash(s)
    426  - d = string(id)
    427  - }
    428  - 
    429  - s.id = artifact.ID(strings.TrimPrefix(d, "sha256:"))
    430  - s.Metadata.ID = strings.TrimPrefix(d, "sha256:")
    431  -}
    432  - 
    433  -func calculateChainID(lm []LayerMetadata) string {
    434  - if len(lm) < 1 {
    435  - return ""
    436  - }
    437  - 
    438  - // DiffID(L0) = digest of layer 0
    439  - // https://github.com/anchore/stereoscope/blob/1b1b744a919964f38d14e1416fb3f25221b761ce/pkg/image/layer_metadata.go#L19-L32
    440  - chainID := lm[0].Digest
    441  - id := chain(chainID, lm[1:])
    442  - 
    443  - return id
    444  -}
    445  - 
    446  -func chain(chainID string, layers []LayerMetadata) string {
    447  - if len(layers) < 1 {
    448  - return chainID
    449  - }
    450  - 
    451  - chainID = digest.FromString(layers[0].Digest + " " + chainID).String()
    452  - return chain(chainID, layers[1:])
    453  -}
    454  - 
    455  -func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
    456  - switch s.Metadata.Scheme {
    457  - case DirectoryScheme, FileScheme:
    458  - s.mutex.Lock()
    459  - defer s.mutex.Unlock()
    460  - if s.directoryResolver == nil {
    461  - exclusionFunctions, err := getDirectoryExclusionFunctions(s.path, s.Exclusions)
    462  - if err != nil {
    463  - return nil, err
    464  - }
    465  - resolver, err := newDirectoryResolver(s.path, s.base, exclusionFunctions...)
    466  - if err != nil {
    467  - return nil, fmt.Errorf("unable to create directory resolver: %w", err)
    468  - }
    469  - s.directoryResolver = resolver
    470  - }
    471  - return s.directoryResolver, nil
    472  - case ImageScheme:
    473  - var resolver FileResolver
    474  - var err error
    475  - switch scope {
    476  - case SquashedScope:
    477  - resolver, err = newImageSquashResolver(s.Image)
    478  - case AllLayersScope:
    479  - resolver, err = newAllLayersResolver(s.Image)
    480  - default:
    481  - return nil, fmt.Errorf("bad image scope provided: %+v", scope)
    482  - }
    483  - if err != nil {
    484  - return nil, err
    485  - }
    486  - // image tree contains all paths, so we filter out the excluded entries afterwards
    487  - if len(s.Exclusions) > 0 {
    488  - resolver = NewExcludingResolver(resolver, getImageExclusionFunction(s.Exclusions))
    489  - }
    490  - return resolver, nil
    491  - }
    492  - return nil, fmt.Errorf("unable to determine FilePathResolver with current scheme=%q", s.Metadata.Scheme)
    493  -}
    494  - 
    495  -func unarchiveToTmp(path string, unarchiver archiver.Unarchiver) (string, func(), error) {
    496  - tempDir, err := os.MkdirTemp("", "syft-archive-contents-")
    497  - if err != nil {
    498  - return "", func() {}, fmt.Errorf("unable to create tempdir for archive processing: %w", err)
    499  - }
    500  - 
    501  - cleanupFn := func() {
    502  - if err := os.RemoveAll(tempDir); err != nil {
    503  - log.Warnf("unable to cleanup archive tempdir: %+v", err)
    504  - }
    505  - }
    506  - 
    507  - return tempDir, cleanupFn, unarchiver.Unarchive(path, tempDir)
    508  -}
    509  - 
    510  -func getImageExclusionFunction(exclusions []string) func(string) bool {
    511  - if len(exclusions) == 0 {
    512  - return nil
    513  - }
    514  - // add subpath exclusions
    515  - for _, exclusion := range exclusions {
    516  - exclusions = append(exclusions, exclusion+"/**")
    517  - }
    518  - return func(path string) bool {
    519  - for _, exclusion := range exclusions {
    520  - matches, err := doublestar.Match(exclusion, path)
    521  - if err != nil {
    522  - return false
    523  - }
    524  - if matches {
    525  - return true
    526  - }
    527  - }
    528  - return false
    529  - }
    530  -}
    531  - 
    532  -func getDirectoryExclusionFunctions(root string, exclusions []string) ([]pathIndexVisitor, error) {
    533  - if len(exclusions) == 0 {
    534  - return nil, nil
    535  - }
    536  - 
    537  - // this is what directoryResolver.indexTree is doing to get the absolute path:
    538  - root, err := filepath.Abs(root)
    539  - if err != nil {
    540  - return nil, err
    541  - }
    542  - 
    543  - // this handles Windows file paths by converting them to C:/something/else format
    544  - root = filepath.ToSlash(root)
    545  - 
    546  - if !strings.HasSuffix(root, "/") {
    547  - root += "/"
    548  - }
    549  - 
    550  - var errors []string
    551  - for idx, exclusion := range exclusions {
    552  - // check exclusions for supported paths, these are all relative to the "scan root"
    553  - if strings.HasPrefix(exclusion, "./") || strings.HasPrefix(exclusion, "*/") || strings.HasPrefix(exclusion, "**/") {
    554  - exclusion = strings.TrimPrefix(exclusion, "./")
    555  - exclusions[idx] = root + exclusion
    556  - } else {
    557  - errors = append(errors, exclusion)
    558  - }
    559  - }
    560  - 
    561  - if errors != nil {
    562  - return nil, fmt.Errorf("invalid exclusion pattern(s): '%s' (must start with one of: './', '*/', or '**/')", strings.Join(errors, "', '"))
    563  - }
    564  - 
    565  - return []pathIndexVisitor{
    566  - func(path string, info os.FileInfo, _ error) error {
    567  - for _, exclusion := range exclusions {
    568  - // this is required to handle Windows filepaths
    569  - path = filepath.ToSlash(path)
    570  - matches, err := doublestar.Match(exclusion, path)
    571  - if err != nil {
    572  - return nil
    573  - }
    574  - if matches {
    575  - if info != nil && info.IsDir() {
    576  - return filepath.SkipDir
    577  - }
    578  - return errSkipPath
    579  - }
    580  - }
    581  - return nil
    582  - },
    583  - }, nil
    584  -}
     277 +//func (s *Source) ID() artifact.ID {
     278 +// if s.id == "" {
     279 +// s.SetID()
     280 +// }
     281 +// return s.id
     282 +//}
     283 +//
     284 +//func (s *Source) SetID() {
     285 +// var d string
     286 +// switch s.Metadata.Scheme {
     287 +// case DirectoryScheme:
     288 +// d = digest.FromString(s.Metadata.Path).String()
     289 +// case FileScheme:
     290 +// // attempt to use the digest of the contents of the file as the ID
     291 +// file, err := os.Open(s.Metadata.Path)
     292 +// if err != nil {
     293 +// d = digest.FromString(s.Metadata.Path).String()
     294 +// break
     295 +// }
     296 +// defer file.Close()
     297 +// di, err := digest.FromReader(file)
     298 +// if err != nil {
     299 +// d = digest.FromString(s.Metadata.Path).String()
     300 +// break
     301 +// }
     302 +// d = di.String()
     303 +// case ImageScheme:
     304 +// manifestDigest := digest.FromBytes(s.Metadata.ImageMetadata.RawManifest).String()
     305 +// if manifestDigest != "" {
     306 +// d = manifestDigest
     307 +// break
     308 +// }
     309 +//
     310 +// // calcuate chain ID for image sources where manifestDigest is not available
     311 +// // https://github.com/opencontainers/image-spec/blob/main/config.md#layer-chainid
     312 +// d = calculateChainID(s.Metadata.ImageMetadata.Layers)
     313 +// if d == "" {
     314 +// // TODO what happens here if image has no layers?
     315 +// // Is this case possible
     316 +// d = digest.FromString(s.Metadata.ImageMetadata.UserInput).String()
     317 +// }
     318 +// default: // for UnknownScheme we hash the struct
     319 +// id, _ := artifact.IDByHash(s)
     320 +// d = string(id)
     321 +// }
     322 +//
     323 +// s.id = artifact.ID(strings.TrimPrefix(d, "sha256:"))
     324 +// s.Metadata.ID = strings.TrimPrefix(d, "sha256:")
     325 +//}
     326 +//
     327 +//func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
     328 +// switch s.Metadata.Scheme {
     329 +// case DirectoryScheme, FileScheme:
     330 +// s.mutex.Lock()
     331 +// defer s.mutex.Unlock()
     332 +// if s.directoryResolver == nil {
     333 +// exclusionFunctions, err := getDirectoryExclusionFunctions(s.path, s.Exclusions)
     334 +// if err != nil {
     335 +// return nil, err
     336 +// }
     337 +// resolver, err := newDirectoryResolver(s.path, s.base, exclusionFunctions...)
     338 +// if err != nil {
     339 +// return nil, fmt.Errorf("unable to create directory resolver: %w", err)
     340 +// }
     341 +// s.directoryResolver = resolver
     342 +// }
     343 +// return s.directoryResolver, nil
     344 +// case ImageScheme:
     345 +// var resolver FileResolver
     346 +// var err error
     347 +// switch scope {
     348 +// case SquashedScope:
     349 +// resolver, err = newImageSquashResolver(s.Image)
     350 +// case AllLayersScope:
     351 +// resolver, err = newAllLayersResolver(s.Image)
     352 +// default:
     353 +// return nil, fmt.Errorf("bad image scope provided: %+v", scope)
     354 +// }
     355 +// if err != nil {
     356 +// return nil, err
     357 +// }
     358 +// // image tree contains all paths, so we filter out the excluded entries afterwards
     359 +// if len(s.Exclusions) > 0 {
     360 +// resolver = NewExcludingResolver(resolver, getImageExclusionFunction(s.Exclusions))
     361 +// }
     362 +// return resolver, nil
     363 +// }
     364 +// return nil, fmt.Errorf("unable to determine FilePathResolver with current scheme=%q", s.Metadata.Scheme)
     365 +//}
    585 366   
  • ■ ■ ■ ■ ■ ■
    syft/source/source_test.go
    skipped 22 lines
    23 23   
    24 24   "github.com/anchore/stereoscope/pkg/image"
    25 25   "github.com/anchore/stereoscope/pkg/imagetest"
    26  - "github.com/anchore/syft/syft/artifact"
    27 26  )
    28 27   
    29  -func TestParseInput(t *testing.T) {
    30  - tests := []struct {
    31  - name string
    32  - input string
    33  - platform string
    34  - expected Scheme
    35  - errFn require.ErrorAssertionFunc
    36  - }{
    37  - {
    38  - name: "ParseInput parses a file input",
    39  - input: "test-fixtures/image-simple/file-1.txt",
    40  - expected: FileScheme,
    41  - },
    42  - {
    43  - name: "errors out when using platform for non-image scheme",
    44  - input: "test-fixtures/image-simple/file-1.txt",
    45  - platform: "arm64",
    46  - errFn: require.Error,
    47  - },
    48  - }
    49  - 
    50  - for _, test := range tests {
    51  - t.Run(test.name, func(t *testing.T) {
    52  - if test.errFn == nil {
    53  - test.errFn = require.NoError
    54  - }
    55  - sourceInput, err := ParseInput(test.input, test.platform)
    56  - test.errFn(t, err)
    57  - if test.expected != "" {
    58  - require.NotNil(t, sourceInput)
    59  - assert.Equal(t, sourceInput.Scheme, test.expected)
    60  - }
    61  - })
    62  - }
    63  -}
     28 +//func TestParseInput(t *testing.T) {
     29 +// tests := []struct {
     30 +// name string
     31 +// input string
     32 +// platform string
     33 +// expected scheme.Scheme
     34 +// errFn require.ErrorAssertionFunc
     35 +// }{
     36 +// {
     37 +// name: "ParseInput parses a file input",
     38 +// input: "test-fixtures/image-simple/file-1.txt",
     39 +// expected: scheme.FileScheme,
     40 +// },
     41 +// {
     42 +// name: "errors out when using platform for non-image scheme",
     43 +// input: "test-fixtures/image-simple/file-1.txt",
     44 +// platform: "arm64",
     45 +// errFn: require.Error,
     46 +// },
     47 +// }
     48 +//
     49 +// for _, test := range tests {
     50 +// t.Run(test.name, func(t *testing.T) {
     51 +// if test.errFn == nil {
     52 +// test.errFn = require.NoError
     53 +// }
     54 +// sourceInput, err := ParseInput(test.input, test.platform)
     55 +// test.errFn(t, err)
     56 +// if test.expected != "" {
     57 +// require.NotNil(t, sourceInput)
     58 +// assert.Equal(t, sourceInput.Scheme, test.expected)
     59 +// }
     60 +// })
     61 +// }
     62 +//}
    64 63   
    65 64  func TestNewFromImageFails(t *testing.T) {
    66 65   t.Run("no image given", func(t *testing.T) {
    skipped 4 lines
    71 70   })
    72 71  }
    73 72   
    74  -func TestSetID(t *testing.T) {
    75  - layer := image.NewLayer(nil)
    76  - layer.Metadata = image.LayerMetadata{
    77  - Digest: "sha256:6f4fb385d4e698647bf2a450749dfbb7bc2831ec9a730ef4046c78c08d468e89",
    78  - }
    79  - img := image.Image{
    80  - Layers: []*image.Layer{layer},
    81  - }
    82  - 
    83  - tests := []struct {
    84  - name string
    85  - input *Source
    86  - expected artifact.ID
    87  - }{
    88  - {
    89  - name: "source.SetID sets the ID for FileScheme",
    90  - input: &Source{
    91  - Metadata: Metadata{
    92  - Scheme: FileScheme,
    93  - Path: "test-fixtures/image-simple/file-1.txt",
    94  - },
    95  - },
    96  - expected: artifact.ID("55096713247489add592ce977637be868497132b36d1e294a3831925ec64319a"),
    97  - },
    98  - {
    99  - name: "source.SetID sets the ID for ImageScheme",
    100  - input: &Source{
    101  - Image: &img,
    102  - Metadata: Metadata{
    103  - Scheme: ImageScheme,
    104  - },
    105  - },
    106  - expected: artifact.ID("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
    107  - },
    108  - {
    109  - name: "source.SetID sets the ID for DirectoryScheme",
    110  - input: &Source{
    111  - Image: &img,
    112  - Metadata: Metadata{
    113  - Scheme: DirectoryScheme,
    114  - Path: "test-fixtures/image-simple",
    115  - },
    116  - },
    117  - expected: artifact.ID("91db61e5e0ae097ef764796ce85e442a93f2a03e5313d4c7307e9b413f62e8c4"),
    118  - },
    119  - {
    120  - name: "source.SetID sets the ID for UnknownScheme",
    121  - input: &Source{
    122  - Image: &img,
    123  - Metadata: Metadata{
    124  - Scheme: UnknownScheme,
    125  - Path: "test-fixtures/image-simple",
    126  - },
    127  - },
    128  - expected: artifact.ID("1b0dc351e6577b01"),
    129  - },
    130  - }
    131  - 
    132  - for _, test := range tests {
    133  - t.Run(test.name, func(t *testing.T) {
    134  - test.input.SetID()
    135  - assert.Equal(t, test.expected, test.input.ID())
    136  - })
    137  - }
    138  -}
     73 +//func TestSetID(t *testing.T) {
     74 +// layer := image.NewLayer(nil)
     75 +// layer.Metadata = image.LayerMetadata{
     76 +// Digest: "sha256:6f4fb385d4e698647bf2a450749dfbb7bc2831ec9a730ef4046c78c08d468e89",
     77 +// }
     78 +// img := image.Image{
     79 +// Layers: []*image.Layer{layer},
     80 +// }
     81 +//
     82 +// tests := []struct {
     83 +// name string
     84 +// input *Source
     85 +// expected artifact.ID
     86 +// }{
     87 +// {
     88 +// name: "source.SetID sets the ID for FileScheme",
     89 +// input: &Source{
     90 +// Metadata: Metadata{
     91 +// Scheme: scheme.FileScheme,
     92 +// Path: "test-fixtures/image-simple/file-1.txt",
     93 +// },
     94 +// },
     95 +// expected: artifact.ID("55096713247489add592ce977637be868497132b36d1e294a3831925ec64319a"),
     96 +// },
     97 +// {
     98 +// name: "source.SetID sets the ID for ImageScheme",
     99 +// input: &Source{
     100 +// Image: &img,
     101 +// Metadata: Metadata{
     102 +// Scheme: scheme.ImageScheme,
     103 +// },
     104 +// },
     105 +// expected: artifact.ID("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
     106 +// },
     107 +// {
     108 +// name: "source.SetID sets the ID for DirectoryScheme",
     109 +// input: &Source{
     110 +// Image: &img,
     111 +// Metadata: Metadata{
     112 +// Scheme: scheme.DirectoryScheme,
     113 +// Path: "test-fixtures/image-simple",
     114 +// },
     115 +// },
     116 +// expected: artifact.ID("91db61e5e0ae097ef764796ce85e442a93f2a03e5313d4c7307e9b413f62e8c4"),
     117 +// },
     118 +// {
     119 +// name: "source.SetID sets the ID for UnknownScheme",
     120 +// input: &Source{
     121 +// Image: &img,
     122 +// Metadata: Metadata{
     123 +// Scheme: scheme.UnknownScheme,
     124 +// Path: "test-fixtures/image-simple",
     125 +// },
     126 +// },
     127 +// expected: artifact.ID("1b0dc351e6577b01"),
     128 +// },
     129 +// }
     130 +//
     131 +// for _, test := range tests {
     132 +// t.Run(test.name, func(t *testing.T) {
     133 +// test.input.SetID()
     134 +// assert.Equal(t, test.expected, test.input.ID())
     135 +// })
     136 +// }
     137 +//}
    139 138   
    140 139  func TestNewFromImage(t *testing.T) {
    141 140   layer := image.NewLayer(nil)
    skipped 796 lines
Please wait...
Page is in error, reload to recover