mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 08:23:15 +01:00
split catalogers into two sets, one for images another for directory scans
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
1c320a8382
commit
10b44f5311
2
Makefile
2
Makefile
@ -130,7 +130,7 @@ unit: fixtures ## Run unit tests (with coverage)
|
|||||||
.PHONY: integration
|
.PHONY: integration
|
||||||
integration: ## Run integration tests
|
integration: ## Run integration tests
|
||||||
$(call title,Running integration tests)
|
$(call title,Running integration tests)
|
||||||
go test -v -tags=integration ./test/integration
|
go test -tags=integration ./test/integration
|
||||||
|
|
||||||
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
|
# note: this is used by CI to determine if the integration test fixture cache (docker image tars) should be busted
|
||||||
integration-fingerprint:
|
integration-fingerprint:
|
||||||
|
|||||||
@ -44,7 +44,7 @@ func parseGemfileLockEntries(_ string, reader io.Reader) ([]pkg.Package, error)
|
|||||||
Name: candidate[0],
|
Name: candidate[0],
|
||||||
Version: strings.Trim(candidate[1], "()"),
|
Version: strings.Trim(candidate[1], "()"),
|
||||||
Language: pkg.Ruby,
|
Language: pkg.Ruby,
|
||||||
Type: pkg.BundlerPkg,
|
Type: pkg.GemPkg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,7 +94,7 @@ func TestParseGemfileLockEntries(t *testing.T) {
|
|||||||
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Type != pkg.BundlerPkg {
|
if a.Type != pkg.GemPkg {
|
||||||
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@ func parseGemspecEntries(_ string, reader io.Reader) ([]pkg.Package, error) {
|
|||||||
Name: metadata.Name,
|
Name: metadata.Name,
|
||||||
Version: metadata.Version,
|
Version: metadata.Version,
|
||||||
Language: pkg.Ruby,
|
Language: pkg.Ruby,
|
||||||
Type: pkg.BundlerPkg,
|
Type: pkg.GemPkg,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,7 +43,7 @@ func TestParseGemspec(t *testing.T) {
|
|||||||
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
t.Errorf("bad language (pkg=%+v): %+v", a.Name, a.Language)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Type != pkg.BundlerPkg {
|
if a.Type != pkg.GemPkg {
|
||||||
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
t.Errorf("bad package type (pkg=%+v): %+v", a.Name, a.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,16 +33,30 @@ type Cataloger interface {
|
|||||||
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
|
// TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering).
|
||||||
}
|
}
|
||||||
|
|
||||||
// All returns a slice of all locally defined catalogers (defined in child packages).
|
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
||||||
func All() []Cataloger {
|
func ImageCatalogers() []Cataloger {
|
||||||
return []Cataloger{
|
return []Cataloger{
|
||||||
|
bundler.NewGemspecCataloger(),
|
||||||
|
python.NewPythonCataloger(), // TODO: split and replace me
|
||||||
|
javascript.NewJavascriptCataloger(), // TODO: split and replace me
|
||||||
|
deb.NewDpkgdbCataloger(),
|
||||||
|
rpmdb.NewRpmdbCataloger(),
|
||||||
|
java.NewJavaCataloger(),
|
||||||
|
apkdb.NewApkdbCataloger(),
|
||||||
|
golang.NewGoModCataloger(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
|
||||||
|
func DirectoryCatalogers() []Cataloger {
|
||||||
|
return []Cataloger{
|
||||||
|
bundler.NewGemfileLockCataloger(),
|
||||||
|
python.NewPythonCataloger(), // TODO: split and replace me
|
||||||
|
javascript.NewJavascriptCataloger(), // TODO: split and replace me
|
||||||
deb.NewDpkgdbCataloger(),
|
deb.NewDpkgdbCataloger(),
|
||||||
bundler.NewGemfileLockCataloger(),
|
|
||||||
python.NewPythonCataloger(),
|
|
||||||
rpmdb.NewRpmdbCataloger(),
|
rpmdb.NewRpmdbCataloger(),
|
||||||
java.NewJavaCataloger(),
|
java.NewJavaCataloger(),
|
||||||
apkdb.NewApkdbCataloger(),
|
apkdb.NewApkdbCataloger(),
|
||||||
golang.NewGoModCataloger(),
|
golang.NewGoModCataloger(),
|
||||||
javascript.NewJavascriptCataloger(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
syft/lib.go
20
syft/lib.go
@ -17,6 +17,8 @@ Similar to the cataloging process, Linux distribution identification is also per
|
|||||||
package syft
|
package syft
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/cataloger"
|
"github.com/anchore/syft/syft/cataloger"
|
||||||
@ -64,15 +66,17 @@ func CatalogFromScope(s scope.Scope) (*pkg.Catalog, error) {
|
|||||||
log.Info("building the catalog")
|
log.Info("building the catalog")
|
||||||
|
|
||||||
// conditionally have two sets of catalogers
|
// conditionally have two sets of catalogers
|
||||||
//var catalogers []cataloger.Cataloger
|
var catalogers []cataloger.Cataloger
|
||||||
//// if image
|
switch s.Scheme {
|
||||||
//// use one set of catalogers
|
case scope.ImageScheme:
|
||||||
//catalogers = ...
|
catalogers = cataloger.ImageCatalogers()
|
||||||
//
|
case scope.DirectoryScheme:
|
||||||
//// if dir
|
catalogers = cataloger.DirectoryCatalogers()
|
||||||
//// use another set of catalogers
|
default:
|
||||||
|
return nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", s.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
return cataloger.Catalog(s.Resolver, cataloger.All()...)
|
return cataloger.Catalog(s.Resolver, catalogers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLogger sets the logger object used for all syft logging calls.
|
// SetLogger sets the logger object used for all syft logging calls.
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/distro"
|
"github.com/anchore/syft/syft/distro"
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
"github.com/sergi/go-diff/diffmatchpatch"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPackage_pURL(t *testing.T) {
|
func TestPackage_pURL(t *testing.T) {
|
||||||
@ -56,7 +57,7 @@ func TestPackage_pURL(t *testing.T) {
|
|||||||
pkg: Package{
|
pkg: Package{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Version: "v0.1.0",
|
Version: "v0.1.0",
|
||||||
Type: BundlerPkg,
|
Type: GemPkg,
|
||||||
},
|
},
|
||||||
expected: "pkg:gem/name@v0.1.0",
|
expected: "pkg:gem/name@v0.1.0",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,7 +8,7 @@ type Type string
|
|||||||
const (
|
const (
|
||||||
UnknownPkg Type = "UnknownPackage"
|
UnknownPkg Type = "UnknownPackage"
|
||||||
ApkPkg Type = "apk"
|
ApkPkg Type = "apk"
|
||||||
BundlerPkg Type = "bundle"
|
GemPkg Type = "gem"
|
||||||
DebPkg Type = "deb"
|
DebPkg Type = "deb"
|
||||||
EggPkg Type = "egg"
|
EggPkg Type = "egg"
|
||||||
// PacmanPkg Type = "pacman"
|
// PacmanPkg Type = "pacman"
|
||||||
@ -26,7 +26,7 @@ const (
|
|||||||
|
|
||||||
var AllPkgs = []Type{
|
var AllPkgs = []Type{
|
||||||
ApkPkg,
|
ApkPkg,
|
||||||
BundlerPkg,
|
GemPkg,
|
||||||
DebPkg,
|
DebPkg,
|
||||||
EggPkg,
|
EggPkg,
|
||||||
// PacmanPkg,
|
// PacmanPkg,
|
||||||
@ -45,7 +45,7 @@ func (t Type) PackageURLType() string {
|
|||||||
switch t {
|
switch t {
|
||||||
case ApkPkg:
|
case ApkPkg:
|
||||||
return "alpine"
|
return "alpine"
|
||||||
case BundlerPkg:
|
case GemPkg:
|
||||||
return packageurl.TypeGem
|
return packageurl.TypeGem
|
||||||
case DebPkg:
|
case DebPkg:
|
||||||
return "deb"
|
return "deb"
|
||||||
|
|||||||
@ -34,9 +34,7 @@ func NewPresenter(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) *Present
|
|||||||
func (pres *Presenter) Present(output io.Writer) error {
|
func (pres *Presenter) Present(output io.Writer) error {
|
||||||
bom := NewDocumentFromCatalog(pres.catalog, pres.distro)
|
bom := NewDocumentFromCatalog(pres.catalog, pres.distro)
|
||||||
|
|
||||||
srcObj := pres.scope.Source()
|
switch src := pres.scope.Source.(type) {
|
||||||
|
|
||||||
switch src := srcObj.(type) {
|
|
||||||
case scope.DirSource:
|
case scope.DirSource:
|
||||||
bom.BomDescriptor.Component = &BdComponent{
|
bom.BomDescriptor.Component = &BdComponent{
|
||||||
Component: Component{
|
Component: Component{
|
||||||
|
|||||||
@ -15,8 +15,7 @@ type ImageLocation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewLocations(p *pkg.Package, s scope.Scope) (Locations, error) {
|
func NewLocations(p *pkg.Package, s scope.Scope) (Locations, error) {
|
||||||
srcObj := s.Source()
|
switch src := s.Source.(type) {
|
||||||
switch src := srcObj.(type) {
|
|
||||||
case scope.ImageSource:
|
case scope.ImageSource:
|
||||||
locations := make([]ImageLocation, len(p.Source))
|
locations := make([]ImageLocation, len(p.Source))
|
||||||
for idx := range p.Source {
|
for idx := range p.Source {
|
||||||
|
|||||||
@ -12,8 +12,7 @@ type Source struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSource(s scope.Scope) (Source, error) {
|
func NewSource(s scope.Scope) (Source, error) {
|
||||||
srcObj := s.Source()
|
switch src := s.Source.(type) {
|
||||||
switch src := srcObj.(type) {
|
|
||||||
case scope.ImageSource:
|
case scope.ImageSource:
|
||||||
return Source{
|
return Source{
|
||||||
Type: "image",
|
Type: "image",
|
||||||
@ -22,7 +21,7 @@ func NewSource(s scope.Scope) (Source, error) {
|
|||||||
case scope.DirSource:
|
case scope.DirSource:
|
||||||
return Source{
|
return Source{
|
||||||
Type: "directory",
|
Type: "directory",
|
||||||
Target: s.DirSrc.Path,
|
Target: src.Path,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return Source{}, fmt.Errorf("unsupported source: %T", src)
|
return Source{}, fmt.Errorf("unsupported source: %T", src)
|
||||||
|
|||||||
@ -27,9 +27,8 @@ func (pres *Presenter) Present(output io.Writer) error {
|
|||||||
// init the tabular writer
|
// init the tabular writer
|
||||||
w := new(tabwriter.Writer)
|
w := new(tabwriter.Writer)
|
||||||
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||||
srcObj := pres.scope.Source()
|
|
||||||
|
|
||||||
switch src := srcObj.(type) {
|
switch src := pres.scope.Source.(type) {
|
||||||
case scope.DirSource:
|
case scope.DirSource:
|
||||||
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", src.Path))
|
fmt.Fprintln(w, fmt.Sprintf("[Path: %s]", src.Path))
|
||||||
case scope.ImageSource:
|
case scope.ImageSource:
|
||||||
|
|||||||
@ -20,12 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
unknownScheme scheme = "unknown-scheme"
|
UnknownScheme Scheme = "unknown-scheme"
|
||||||
directoryScheme scheme = "directory-scheme"
|
DirectoryScheme Scheme = "directory-scheme"
|
||||||
imageScheme scheme = "image-scheme"
|
ImageScheme Scheme = "image-scheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
type scheme string
|
type Scheme string
|
||||||
|
|
||||||
// ImageSource represents a data source that is a container image
|
// ImageSource represents a data source that is a container image
|
||||||
type ImageSource struct {
|
type ImageSource struct {
|
||||||
@ -42,8 +42,8 @@ type DirSource struct {
|
|||||||
type Scope struct {
|
type Scope struct {
|
||||||
Option Option // specific perspective to catalog
|
Option Option // specific perspective to catalog
|
||||||
Resolver Resolver // a Resolver object to use in file path/glob resolution and file contents resolution
|
Resolver Resolver // a Resolver object to use in file path/glob resolution and file contents resolution
|
||||||
ImgSrc ImageSource // the specific image to be cataloged
|
Source interface{} // the specific source object to be cataloged
|
||||||
DirSrc DirSource // the specific directory to be cataloged
|
Scheme Scheme // the source data scheme type (directory or image)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScope produces a Scope based on userInput like dir: or image:tag
|
// NewScope produces a Scope based on userInput like dir: or image:tag
|
||||||
@ -55,7 +55,7 @@ func NewScope(userInput string, o Option) (Scope, func(), error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch parsedScheme {
|
switch parsedScheme {
|
||||||
case directoryScheme:
|
case DirectoryScheme:
|
||||||
fileMeta, err := fs.Stat(location)
|
fileMeta, err := fs.Stat(location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Scope{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
return Scope{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err)
|
||||||
@ -71,7 +71,7 @@ func NewScope(userInput string, o Option) (Scope, func(), error) {
|
|||||||
}
|
}
|
||||||
return s, func() {}, nil
|
return s, func() {}, nil
|
||||||
|
|
||||||
case imageScheme:
|
case ImageScheme:
|
||||||
img, err := stereoscope.GetImage(location)
|
img, err := stereoscope.GetImage(location)
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
stereoscope.Cleanup()
|
stereoscope.Cleanup()
|
||||||
@ -97,9 +97,10 @@ func NewScopeFromDir(path string) (Scope, error) {
|
|||||||
Resolver: &resolvers.DirectoryResolver{
|
Resolver: &resolvers.DirectoryResolver{
|
||||||
Path: path,
|
Path: path,
|
||||||
},
|
},
|
||||||
DirSrc: DirSource{
|
Source: DirSource{
|
||||||
Path: path,
|
Path: path,
|
||||||
},
|
},
|
||||||
|
Scheme: DirectoryScheme,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,59 +119,48 @@ func NewScopeFromImage(img *image.Image, option Option) (Scope, error) {
|
|||||||
return Scope{
|
return Scope{
|
||||||
Option: option,
|
Option: option,
|
||||||
Resolver: resolver,
|
Resolver: resolver,
|
||||||
ImgSrc: ImageSource{
|
Source: ImageSource{
|
||||||
Img: img,
|
Img: img,
|
||||||
},
|
},
|
||||||
|
Scheme: ImageScheme,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source returns the configured data source (either a dir source or container image source)
|
|
||||||
func (s Scope) Source() interface{} {
|
|
||||||
if s.ImgSrc != (ImageSource{}) {
|
|
||||||
return s.ImgSrc
|
|
||||||
}
|
|
||||||
if s.DirSrc != (DirSource{}) {
|
|
||||||
return s.DirSrc
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type sourceDetector func(string) (image.Source, string, error)
|
type sourceDetector func(string) (image.Source, string, error)
|
||||||
|
|
||||||
func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (scheme, string, error) {
|
func detectScheme(fs afero.Fs, imageDetector sourceDetector, userInput string) (Scheme, string, error) {
|
||||||
if strings.HasPrefix(userInput, "dir:") {
|
if strings.HasPrefix(userInput, "dir:") {
|
||||||
// blindly trust the user's scheme
|
// blindly trust the user's scheme
|
||||||
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
dirLocation, err := homedir.Expand(strings.TrimPrefix(userInput, "dir:"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownScheme, "", fmt.Errorf("unable to expand directory path: %w", err)
|
return UnknownScheme, "", fmt.Errorf("unable to expand directory path: %w", err)
|
||||||
}
|
}
|
||||||
return directoryScheme, dirLocation, nil
|
return DirectoryScheme, dirLocation, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should attempt to let stereoscope determine what the source is first --just because the source is a valid directory
|
// we should attempt to let stereoscope determine what the source is first --just because the source is a valid directory
|
||||||
// doesn't mean we yet know if it is an OCI layout directory (to be treated as an image) or if it is a generic filesystem directory.
|
// doesn't mean we yet know if it is an OCI layout directory (to be treated as an image) or if it is a generic filesystem directory.
|
||||||
source, imageSpec, err := imageDetector(userInput)
|
source, imageSpec, err := imageDetector(userInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownScheme, "", fmt.Errorf("unable to detect the scheme from %q: %w", userInput, err)
|
return UnknownScheme, "", fmt.Errorf("unable to detect the scheme from %q: %w", userInput, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if source == image.UnknownSource {
|
if source == image.UnknownSource {
|
||||||
dirLocation, err := homedir.Expand(userInput)
|
dirLocation, err := homedir.Expand(userInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownScheme, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
return UnknownScheme, "", fmt.Errorf("unable to expand potential directory path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileMeta, err := fs.Stat(dirLocation)
|
fileMeta, err := fs.Stat(dirLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unknownScheme, "", nil
|
return UnknownScheme, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileMeta.IsDir() {
|
if fileMeta.IsDir() {
|
||||||
return directoryScheme, dirLocation, nil
|
return DirectoryScheme, dirLocation, nil
|
||||||
}
|
}
|
||||||
return unknownScheme, "", nil
|
return UnknownScheme, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return imageScheme, imageSpec, nil
|
return ImageScheme, imageSpec, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
package scope
|
package scope
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mitchellh/go-homedir"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/stereoscope/pkg/file"
|
"github.com/anchore/stereoscope/pkg/file"
|
||||||
"github.com/anchore/stereoscope/pkg/image"
|
"github.com/anchore/stereoscope/pkg/image"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewScopeFromImageFails(t *testing.T) {
|
func TestNewScopeFromImageFails(t *testing.T) {
|
||||||
@ -78,8 +78,8 @@ func TestDirectoryScope(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could not create NewDirScope: %w", err)
|
t.Errorf("could not create NewDirScope: %w", err)
|
||||||
}
|
}
|
||||||
if p.DirSrc.Path != test.input {
|
if p.Source.(DirSource).Path != test.input {
|
||||||
t.Errorf("mismatched stringer: '%s' != '%s'", p.DirSrc.Path, test.input)
|
t.Errorf("mismatched stringer: '%s' != '%s'", p.Source.(DirSource).Path, test.input)
|
||||||
}
|
}
|
||||||
|
|
||||||
refs, err := p.Resolver.FilesByPath(test.inputPaths...)
|
refs, err := p.Resolver.FilesByPath(test.inputPaths...)
|
||||||
@ -229,7 +229,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
userInput string
|
userInput string
|
||||||
dirs []string
|
dirs []string
|
||||||
detection detectorResult
|
detection detectorResult
|
||||||
expectedScheme scheme
|
expectedScheme Scheme
|
||||||
expectedLocation string
|
expectedLocation string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -239,7 +239,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "wagoodman/dive:latest",
|
ref: "wagoodman/dive:latest",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "wagoodman/dive:latest",
|
expectedLocation: "wagoodman/dive:latest",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -249,7 +249,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "wagoodman/dive",
|
ref: "wagoodman/dive",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "wagoodman/dive",
|
expectedLocation: "wagoodman/dive",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -259,7 +259,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "wagoodman/dive:latest",
|
ref: "wagoodman/dive:latest",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "wagoodman/dive:latest",
|
expectedLocation: "wagoodman/dive:latest",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -269,7 +269,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "wagoodman/dive",
|
ref: "wagoodman/dive",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "wagoodman/dive",
|
expectedLocation: "wagoodman/dive",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -279,7 +279,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "latest",
|
ref: "latest",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||||
// the user will need to provide more explicit input (docker:docker:latest)
|
// the user will need to provide more explicit input (docker:docker:latest)
|
||||||
expectedLocation: "latest",
|
expectedLocation: "latest",
|
||||||
@ -291,7 +291,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "docker:latest",
|
ref: "docker:latest",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
// we want to be able to handle this case better, however, I don't see a way to do this
|
// we want to be able to handle this case better, however, I don't see a way to do this
|
||||||
// the user will need to provide more explicit input (docker:docker:latest)
|
// the user will need to provide more explicit input (docker:docker:latest)
|
||||||
expectedLocation: "docker:latest",
|
expectedLocation: "docker:latest",
|
||||||
@ -303,7 +303,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.OciTarballSource,
|
src: image.OciTarballSource,
|
||||||
ref: "some/path-to-file",
|
ref: "some/path-to-file",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "some/path-to-file",
|
expectedLocation: "some/path-to-file",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -314,7 +314,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
ref: "some/path-to-dir",
|
ref: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
dirs: []string{"some/path-to-dir"},
|
dirs: []string{"some/path-to-dir"},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "some/path-to-dir",
|
expectedLocation: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -325,7 +325,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
ref: "",
|
ref: "",
|
||||||
},
|
},
|
||||||
dirs: []string{"some/path-to-dir"},
|
dirs: []string{"some/path-to-dir"},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: "some/path-to-dir",
|
expectedLocation: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -335,7 +335,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.DockerDaemonSource,
|
src: image.DockerDaemonSource,
|
||||||
ref: "some/path-to-dir",
|
ref: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "some/path-to-dir",
|
expectedLocation: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -346,7 +346,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
ref: "",
|
ref: "",
|
||||||
},
|
},
|
||||||
dirs: []string{"some/path-to-dir"},
|
dirs: []string{"some/path-to-dir"},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: "some/path-to-dir",
|
expectedLocation: "some/path-to-dir",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -356,7 +356,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.UnknownSource,
|
src: image.UnknownSource,
|
||||||
ref: "",
|
ref: "",
|
||||||
},
|
},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: ".",
|
expectedLocation: ".",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -366,7 +366,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.UnknownSource,
|
src: image.UnknownSource,
|
||||||
ref: "",
|
ref: "",
|
||||||
},
|
},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: ".",
|
expectedLocation: ".",
|
||||||
},
|
},
|
||||||
// we should support tilde expansion
|
// we should support tilde expansion
|
||||||
@ -377,7 +377,7 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
src: image.OciDirectorySource,
|
src: image.OciDirectorySource,
|
||||||
ref: "~/some-path",
|
ref: "~/some-path",
|
||||||
},
|
},
|
||||||
expectedScheme: imageScheme,
|
expectedScheme: ImageScheme,
|
||||||
expectedLocation: "~/some-path",
|
expectedLocation: "~/some-path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -388,26 +388,26 @@ func TestDetectScheme(t *testing.T) {
|
|||||||
ref: "",
|
ref: "",
|
||||||
},
|
},
|
||||||
dirs: []string{"~/some-path"},
|
dirs: []string{"~/some-path"},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: "~/some-path",
|
expectedLocation: "~/some-path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tilde-expansion-dir-explicit-exists",
|
name: "tilde-expansion-dir-explicit-exists",
|
||||||
userInput: "dir:~/some-path",
|
userInput: "dir:~/some-path",
|
||||||
dirs: []string{"~/some-path"},
|
dirs: []string{"~/some-path"},
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: "~/some-path",
|
expectedLocation: "~/some-path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tilde-expansion-dir-explicit-dne",
|
name: "tilde-expansion-dir-explicit-dne",
|
||||||
userInput: "dir:~/some-path",
|
userInput: "dir:~/some-path",
|
||||||
expectedScheme: directoryScheme,
|
expectedScheme: DirectoryScheme,
|
||||||
expectedLocation: "~/some-path",
|
expectedLocation: "~/some-path",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "tilde-expansion-dir-implicit-dne",
|
name: "tilde-expansion-dir-implicit-dne",
|
||||||
userInput: "~/some-path",
|
userInput: "~/some-path",
|
||||||
expectedScheme: unknownScheme,
|
expectedScheme: UnknownScheme,
|
||||||
expectedLocation: "",
|
expectedLocation: "",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,6 +108,10 @@ func TestJsonSchemaImg(t *testing.T) {
|
|||||||
t.Fatalf("failed to catalog image: %+v", err)
|
t.Fatalf("failed to catalog image: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cases []testCase
|
||||||
|
cases = append(cases, commonTestCases...)
|
||||||
|
cases = append(cases, imageOnlyTestCases...)
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
testJsonSchema(t, catalog, theScope, "img")
|
testJsonSchema(t, catalog, theScope, "img")
|
||||||
@ -121,6 +125,10 @@ func TestJsonSchemaDirs(t *testing.T) {
|
|||||||
t.Errorf("unable to create scope from dir: %+v", err)
|
t.Errorf("unable to create scope from dir: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cases []testCase
|
||||||
|
cases = append(cases, commonTestCases...)
|
||||||
|
cases = append(cases, dirOnlyTestCases...)
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
testJsonSchema(t, catalog, theScope, "dir")
|
testJsonSchema(t, catalog, theScope, "dir")
|
||||||
|
|||||||
@ -4,12 +4,86 @@ package integration
|
|||||||
|
|
||||||
import "github.com/anchore/syft/syft/pkg"
|
import "github.com/anchore/syft/syft/pkg"
|
||||||
|
|
||||||
var cases = []struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
pkgType pkg.Type
|
pkgType pkg.Type
|
||||||
pkgLanguage pkg.Language
|
pkgLanguage pkg.Language
|
||||||
pkgInfo map[string]string
|
pkgInfo map[string]string
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var imageOnlyTestCases = []testCase{
|
||||||
|
{
|
||||||
|
name: "find gemspec packages",
|
||||||
|
pkgType: pkg.GemPkg,
|
||||||
|
pkgLanguage: pkg.Ruby,
|
||||||
|
pkgInfo: map[string]string{
|
||||||
|
"bundler": "2.1.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var dirOnlyTestCases = []testCase{
|
||||||
|
{
|
||||||
|
name: "find gemfile packages",
|
||||||
|
pkgType: pkg.GemPkg,
|
||||||
|
pkgLanguage: pkg.Ruby,
|
||||||
|
pkgInfo: map[string]string{
|
||||||
|
"actionmailer": "4.1.1",
|
||||||
|
"actionpack": "4.1.1",
|
||||||
|
"actionview": "4.1.1",
|
||||||
|
"activemodel": "4.1.1",
|
||||||
|
"activerecord": "4.1.1",
|
||||||
|
"activesupport": "4.1.1",
|
||||||
|
"arel": "5.0.1.20140414130214",
|
||||||
|
"bootstrap-sass": "3.1.1.1",
|
||||||
|
"builder": "3.2.2",
|
||||||
|
"coffee-rails": "4.0.1",
|
||||||
|
"coffee-script": "2.2.0",
|
||||||
|
"coffee-script-source": "1.7.0",
|
||||||
|
"erubis": "2.7.0",
|
||||||
|
"execjs": "2.0.2",
|
||||||
|
"hike": "1.2.3",
|
||||||
|
"i18n": "0.6.9",
|
||||||
|
"jbuilder": "2.0.7",
|
||||||
|
"jquery-rails": "3.1.0",
|
||||||
|
"json": "1.8.1",
|
||||||
|
"kgio": "2.9.2",
|
||||||
|
"libv8": "3.16.14.3",
|
||||||
|
"mail": "2.5.4",
|
||||||
|
"mime-types": "1.25.1",
|
||||||
|
"minitest": "5.3.4",
|
||||||
|
"multi_json": "1.10.1",
|
||||||
|
"mysql2": "0.3.16",
|
||||||
|
"polyglot": "0.3.4",
|
||||||
|
"rack": "1.5.2",
|
||||||
|
"rack-test": "0.6.2",
|
||||||
|
"rails": "4.1.1",
|
||||||
|
"railties": "4.1.1",
|
||||||
|
"raindrops": "0.13.0",
|
||||||
|
"rake": "10.3.2",
|
||||||
|
"rdoc": "4.1.1",
|
||||||
|
"ref": "1.0.5",
|
||||||
|
"sass": "3.2.19",
|
||||||
|
"sass-rails": "4.0.3",
|
||||||
|
"sdoc": "0.4.0",
|
||||||
|
"spring": "1.1.3",
|
||||||
|
"sprockets": "2.11.0",
|
||||||
|
"sprockets-rails": "2.1.3",
|
||||||
|
"sqlite3": "1.3.9",
|
||||||
|
"therubyracer": "0.12.1",
|
||||||
|
"thor": "0.19.1",
|
||||||
|
"thread_safe": "0.3.3",
|
||||||
|
"tilt": "1.4.1",
|
||||||
|
"treetop": "1.4.15",
|
||||||
|
"turbolinks": "2.2.2",
|
||||||
|
"tzinfo": "1.2.0",
|
||||||
|
"uglifier": "2.5.0",
|
||||||
|
"unicorn": "4.8.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonTestCases = []testCase{
|
||||||
{
|
{
|
||||||
name: "find rpmdb packages",
|
name: "find rpmdb packages",
|
||||||
pkgType: pkg.RpmPkg,
|
pkgType: pkg.RpmPkg,
|
||||||
@ -98,64 +172,6 @@ var cases = []struct {
|
|||||||
"mypy": "v0.770",
|
"mypy": "v0.770",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "find bundler packages",
|
|
||||||
pkgType: pkg.BundlerPkg,
|
|
||||||
pkgLanguage: pkg.Ruby,
|
|
||||||
pkgInfo: map[string]string{
|
|
||||||
"actionmailer": "4.1.1",
|
|
||||||
"actionpack": "4.1.1",
|
|
||||||
"actionview": "4.1.1",
|
|
||||||
"activemodel": "4.1.1",
|
|
||||||
"activerecord": "4.1.1",
|
|
||||||
"activesupport": "4.1.1",
|
|
||||||
"arel": "5.0.1.20140414130214",
|
|
||||||
"bootstrap-sass": "3.1.1.1",
|
|
||||||
"builder": "3.2.2",
|
|
||||||
"coffee-rails": "4.0.1",
|
|
||||||
"coffee-script": "2.2.0",
|
|
||||||
"coffee-script-source": "1.7.0",
|
|
||||||
"erubis": "2.7.0",
|
|
||||||
"execjs": "2.0.2",
|
|
||||||
"hike": "1.2.3",
|
|
||||||
"i18n": "0.6.9",
|
|
||||||
"jbuilder": "2.0.7",
|
|
||||||
"jquery-rails": "3.1.0",
|
|
||||||
"json": "1.8.1",
|
|
||||||
"kgio": "2.9.2",
|
|
||||||
"libv8": "3.16.14.3",
|
|
||||||
"mail": "2.5.4",
|
|
||||||
"mime-types": "1.25.1",
|
|
||||||
"minitest": "5.3.4",
|
|
||||||
"multi_json": "1.10.1",
|
|
||||||
"mysql2": "0.3.16",
|
|
||||||
"polyglot": "0.3.4",
|
|
||||||
"rack": "1.5.2",
|
|
||||||
"rack-test": "0.6.2",
|
|
||||||
"rails": "4.1.1",
|
|
||||||
"railties": "4.1.1",
|
|
||||||
"raindrops": "0.13.0",
|
|
||||||
"rake": "10.3.2",
|
|
||||||
"rdoc": "4.1.1",
|
|
||||||
"ref": "1.0.5",
|
|
||||||
"sass": "3.2.19",
|
|
||||||
"sass-rails": "4.0.3",
|
|
||||||
"sdoc": "0.4.0",
|
|
||||||
"spring": "1.1.3",
|
|
||||||
"sprockets": "2.11.0",
|
|
||||||
"sprockets-rails": "2.1.3",
|
|
||||||
"sqlite3": "1.3.9",
|
|
||||||
"therubyracer": "0.12.1",
|
|
||||||
"thor": "0.19.1",
|
|
||||||
"thread_safe": "0.3.3",
|
|
||||||
"tilt": "1.4.1",
|
|
||||||
"treetop": "1.4.15",
|
|
||||||
"turbolinks": "2.2.2",
|
|
||||||
"tzinfo": "1.2.0",
|
|
||||||
"uglifier": "2.5.0",
|
|
||||||
"unicorn": "4.8.3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
|
|
||||||
name: "find apkdb packages",
|
name: "find apkdb packages",
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||||
|
"github.com/go-test/deep"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -35,6 +37,10 @@ func TestPkgCoverageImage(t *testing.T) {
|
|||||||
definedPkgs.Add(string(p))
|
definedPkgs.Add(string(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cases []testCase
|
||||||
|
cases = append(cases, commonTestCases...)
|
||||||
|
cases = append(cases, imageOnlyTestCases...)
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
pkgCount := 0
|
pkgCount := 0
|
||||||
@ -81,10 +87,16 @@ func TestPkgCoverageImage(t *testing.T) {
|
|||||||
// ensure that integration test cases stay in sync with the available catalogers
|
// ensure that integration test cases stay in sync with the available catalogers
|
||||||
if len(observedLanguages) < len(definedLanguages) {
|
if len(observedLanguages) < len(definedLanguages) {
|
||||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||||
|
for _, d := range deep.Equal(observedLanguages, definedLanguages) {
|
||||||
|
t.Errorf("diff: %+v", d)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(observedPkgs) < len(definedPkgs) {
|
if len(observedPkgs) < len(definedPkgs) {
|
||||||
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
|
t.Errorf("package coverage incomplete (packages=%d, coverage=%d)", len(definedPkgs), len(observedPkgs))
|
||||||
|
for _, d := range deep.Equal(observedPkgs, definedPkgs) {
|
||||||
|
t.Errorf("diff: %+v", d)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +119,10 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
|||||||
definedPkgs.Add(string(p))
|
definedPkgs.Add(string(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cases []testCase
|
||||||
|
cases = append(cases, commonTestCases...)
|
||||||
|
cases = append(cases, dirOnlyTestCases...)
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
pkgCount := 0
|
pkgCount := 0
|
||||||
@ -150,7 +166,7 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
|||||||
observedPkgs.Remove(string(pkg.UnknownPkg))
|
observedPkgs.Remove(string(pkg.UnknownPkg))
|
||||||
definedPkgs.Remove(string(pkg.UnknownPkg))
|
definedPkgs.Remove(string(pkg.UnknownPkg))
|
||||||
|
|
||||||
// ensure that integration test cases stay in sync with the available catalogers
|
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||||
if len(observedLanguages) < len(definedLanguages) {
|
if len(observedLanguages) < len(definedLanguages) {
|
||||||
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
t.Errorf("language coverage incomplete (languages=%d, coverage=%d)", len(definedLanguages), len(observedLanguages))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
# stub: bundler 2.1.4 ruby lib
|
||||||
|
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "bundler".freeze
|
||||||
|
s.version = "2.1.4"
|
||||||
|
|
||||||
|
s.required_rubygems_version = Gem::Requirement.new(">= 2.5.2".freeze) if s.respond_to? :required_rubygems_version=
|
||||||
|
s.require_paths = ["lib".freeze]
|
||||||
|
s.authors = ["Andr\u00E9 Arko".freeze, "Samuel Giddins".freeze, "Colby Swandale".freeze, "Hiroshi Shibata".freeze, "David Rodr\u00EDguez".freeze, "Grey Baker".f
|
||||||
|
s.bindir = "exe".freeze
|
||||||
|
s.date = "2020-01-05"
|
||||||
|
s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably".freeze
|
||||||
|
s.email = ["team@bundler.io".freeze]
|
||||||
|
s.executables = ["bundle".freeze, "bundler".freeze]
|
||||||
|
s.files = ["exe/bundle".freeze, "exe/bundler".freeze]
|
||||||
|
s.homepage = "https://bundler.io".freeze
|
||||||
|
s.licenses = ["MIT".freeze]
|
||||||
|
s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze)
|
||||||
|
s.rubygems_version = "3.1.2".freeze
|
||||||
|
s.summary = "The best way to manage your application's dependencies".freeze
|
||||||
|
|
||||||
|
s.installed_by_version = "3.1.2" if s.respond_to? :installed_by_version
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user