mirror of
https://github.com/anchore/syft.git
synced 2026-02-13 19:16:43 +01:00
External sources configuration (#1158)
This commit is contained in:
parent
e9221ae25d
commit
13296880cd
@ -26,6 +26,7 @@ type PackagesOptions struct {
|
|||||||
OverwriteExistingImage bool
|
OverwriteExistingImage bool
|
||||||
ImportTimeout uint
|
ImportTimeout uint
|
||||||
Catalogers []string
|
Catalogers []string
|
||||||
|
ExternalSourcesEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Interface = (*PackagesOptions)(nil)
|
var _ Interface = (*PackagesOptions)(nil)
|
||||||
@ -70,9 +71,13 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
|||||||
cmd.Flags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
|
cmd.Flags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
|
||||||
"set a timeout duration (in seconds) for the upload to Anchore Enterprise")
|
"set a timeout duration (in seconds) for the upload to Anchore Enterprise")
|
||||||
|
|
||||||
|
cmd.Flags().BoolVarP(&o.ExternalSourcesEnabled, "external-sources-enabled", "", false,
|
||||||
|
"shut off any use of external sources during sbom generation (default false")
|
||||||
|
|
||||||
return bindPackageConfigOptions(cmd.Flags(), v)
|
return bindPackageConfigOptions(cmd.Flags(), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||||
// Formatting & Input options //////////////////////////////////////////////
|
// Formatting & Input options //////////////////////////////////////////////
|
||||||
|
|
||||||
@ -104,6 +109,10 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := v.BindPFlag("external_sources.external-sources-enabled", flags.Lookup("external-sources-enabled")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Upload options //////////////////////////////////////////////////////////
|
// Upload options //////////////////////////////////////////////////////////
|
||||||
|
|
||||||
if err := v.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
|
if err := v.BindPFlag("anchore.host", flags.Lookup("host")); err != nil {
|
||||||
|
|||||||
@ -57,6 +57,7 @@ type Application struct {
|
|||||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||||
|
ExternalSources ExternalSources `yaml:"external_sources" json:"external_sources" mapstructure:"external_sources"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||||
@ -66,7 +67,8 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
|||||||
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
||||||
Scope: cfg.Package.Cataloger.ScopeOpt,
|
Scope: cfg.Package.Cataloger.ScopeOpt,
|
||||||
},
|
},
|
||||||
Catalogers: cfg.Catalogers,
|
Catalogers: cfg.Catalogers,
|
||||||
|
ExternalSourcesEnabled: cfg.ExternalSources.ExternalSourcesEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
internal/config/datasources.go
Normal file
11
internal/config/datasources.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "github.com/spf13/viper"
|
||||||
|
|
||||||
|
type ExternalSources struct {
|
||||||
|
ExternalSourcesEnabled bool `yaml:"external-sources-enabled" json:"external-sources-enabled" mapstructure:"external-sources-enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExternalSources) loadDefaultValues(v *viper.Viper) {
|
||||||
|
v.SetDefault("external-sources-enabled", false)
|
||||||
|
}
|
||||||
@ -23,6 +23,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return catalogerName
|
return catalogerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the alpmdb cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
fileMatches, err := resolver.FilesByGlob(pkg.AlpmDBGlob)
|
fileMatches, err := resolver.FilesByGlob(pkg.AlpmDBGlob)
|
||||||
|
|||||||
@ -41,6 +41,8 @@ type Cataloger interface {
|
|||||||
Name() string
|
Name() string
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
||||||
Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error)
|
Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error)
|
||||||
|
// UsesExternalSources returns if the cataloger uses external sources, such as querying a database
|
||||||
|
UsesExternalSources() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
|
||||||
@ -58,7 +60,7 @@ func ImageCatalogers(cfg Config) []Cataloger {
|
|||||||
golang.NewGoModuleBinaryCataloger(),
|
golang.NewGoModuleBinaryCataloger(),
|
||||||
dotnet.NewDotnetDepsCataloger(),
|
dotnet.NewDotnetDepsCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
|
// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
|
||||||
@ -84,7 +86,7 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
|
|||||||
cpp.NewConanfileCataloger(),
|
cpp.NewConanfileCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
haskell.NewHackageCataloger(),
|
haskell.NewHackageCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllCatalogers returns all implemented catalogers
|
// AllCatalogers returns all implemented catalogers
|
||||||
@ -114,10 +116,20 @@ func AllCatalogers(cfg Config) []Cataloger {
|
|||||||
cpp.NewConanfileCataloger(),
|
cpp.NewConanfileCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
haskell.NewHackageCataloger(),
|
haskell.NewHackageCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestedAllCatalogers returns true if all Catalogers have been requested. Takes into account cfg.ExternalSourcesEnabled
|
||||||
func RequestedAllCatalogers(cfg Config) bool {
|
func RequestedAllCatalogers(cfg Config) bool {
|
||||||
|
// if external sources are disabled, only return false if there actually are any catalogers that use external sources
|
||||||
|
if !cfg.ExternalSourcesEnabled {
|
||||||
|
for _, cat := range AllCatalogers(Config{Catalogers: []string{"all"}, ExternalSourcesEnabled: true}) {
|
||||||
|
if cat.UsesExternalSources() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, enableCatalogerPattern := range cfg.Catalogers {
|
for _, enableCatalogerPattern := range cfg.Catalogers {
|
||||||
if enableCatalogerPattern == AllCatalogersPattern {
|
if enableCatalogerPattern == AllCatalogersPattern {
|
||||||
return true
|
return true
|
||||||
@ -126,14 +138,33 @@ func RequestedAllCatalogers(cfg Config) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterCatalogers(catalogers []Cataloger, enabledCatalogerPatterns []string) []Cataloger {
|
func filterForExternalSources(catalogers []Cataloger, cfg Config) []Cataloger {
|
||||||
|
if cfg.ExternalSourcesEnabled {
|
||||||
|
return catalogers
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabledCatalogers []Cataloger
|
||||||
|
for _, cataloger := range catalogers {
|
||||||
|
if !cataloger.UsesExternalSources() {
|
||||||
|
enabledCatalogers = append(enabledCatalogers, cataloger)
|
||||||
|
} else {
|
||||||
|
log.Infof("cataloger %v will not be used because external sources are disabled", cataloger.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabledCatalogers
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCatalogers(catalogers []Cataloger, cfg Config) []Cataloger {
|
||||||
|
enabledCatalogerPatterns := cfg.Catalogers
|
||||||
|
|
||||||
// if cataloger is not set, all applicable catalogers are enabled by default
|
// if cataloger is not set, all applicable catalogers are enabled by default
|
||||||
if len(enabledCatalogerPatterns) == 0 {
|
if len(enabledCatalogerPatterns) == 0 {
|
||||||
return catalogers
|
return filterForExternalSources(catalogers, cfg)
|
||||||
}
|
}
|
||||||
for _, enableCatalogerPattern := range enabledCatalogerPatterns {
|
for _, enableCatalogerPattern := range enabledCatalogerPatterns {
|
||||||
if enableCatalogerPattern == AllCatalogersPattern {
|
if enableCatalogerPattern == AllCatalogersPattern {
|
||||||
return catalogers
|
return filterForExternalSources(catalogers, cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var keepCatalogers []Cataloger
|
var keepCatalogers []Cataloger
|
||||||
@ -144,7 +175,7 @@ func filterCatalogers(catalogers []Cataloger, enabledCatalogerPatterns []string)
|
|||||||
}
|
}
|
||||||
log.Infof("skipping cataloger %q", cataloger.Name())
|
log.Infof("skipping cataloger %q", cataloger.Name())
|
||||||
}
|
}
|
||||||
return keepCatalogers
|
return filterForExternalSources(keepCatalogers, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(enabledPartial []string, catalogerName string) bool {
|
func contains(enabledPartial []string, catalogerName string) bool {
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
package cataloger
|
package cataloger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/source"
|
"github.com/anchore/syft/syft/source"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Cataloger = (*dummy)(nil)
|
var _ Cataloger = (*dummy)(nil)
|
||||||
@ -22,12 +23,17 @@ func (d dummy) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relatio
|
|||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d dummy) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func Test_filterCatalogers(t *testing.T) {
|
func Test_filterCatalogers(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
patterns []string
|
patterns []string
|
||||||
catalogers []string
|
ExternalSourcesEnabled bool
|
||||||
want []string
|
catalogers []string
|
||||||
|
want []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no filtering",
|
name: "no filtering",
|
||||||
@ -142,6 +148,21 @@ func Test_filterCatalogers(t *testing.T) {
|
|||||||
"go-module-binary-cataloger",
|
"go-module-binary-cataloger",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{ // Note: no catalogers with external sources are currently implemented
|
||||||
|
name: "external sources enabled",
|
||||||
|
patterns: []string{"all"},
|
||||||
|
ExternalSourcesEnabled: true,
|
||||||
|
catalogers: []string{
|
||||||
|
"ruby-gemspec-cataloger",
|
||||||
|
"python-package-cataloger",
|
||||||
|
"rekor-cataloger",
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"ruby-gemspec-cataloger",
|
||||||
|
"python-package-cataloger",
|
||||||
|
"rekor-cataloger",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@ -149,7 +170,8 @@ func Test_filterCatalogers(t *testing.T) {
|
|||||||
for _, n := range tt.catalogers {
|
for _, n := range tt.catalogers {
|
||||||
catalogers = append(catalogers, dummy{name: n})
|
catalogers = append(catalogers, dummy{name: n})
|
||||||
}
|
}
|
||||||
got := filterCatalogers(catalogers, tt.patterns)
|
cfg := Config{Catalogers: tt.patterns, ExternalSourcesEnabled: tt.ExternalSourcesEnabled}
|
||||||
|
got := filterCatalogers(catalogers, cfg)
|
||||||
var gotNames []string
|
var gotNames []string
|
||||||
for _, g := range got {
|
for _, g := range got {
|
||||||
gotNames = append(gotNames, g.Name())
|
gotNames = append(gotNames, g.Name())
|
||||||
|
|||||||
@ -39,6 +39,11 @@ func (c *GenericCataloger) Name() string {
|
|||||||
return c.upstreamCataloger
|
return c.upstreamCataloger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that any GenericCatalogor does not use external sources
|
||||||
|
func (c *GenericCataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source.
|
||||||
func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *GenericCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var packages []pkg.Package
|
var packages []pkg.Package
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Search SearchConfig
|
Search SearchConfig
|
||||||
Catalogers []string
|
Catalogers []string
|
||||||
|
ExternalSourcesEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
|
|||||||
@ -36,6 +36,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return "dpkgdb-cataloger"
|
return "dpkgdb-cataloger"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the dpkgdb cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing dpkg support files.
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
dbFileMatches, err := resolver.FilesByGlob(pkg.DpkgDBGlob)
|
dbFileMatches, err := resolver.FilesByGlob(pkg.DpkgDBGlob)
|
||||||
|
|||||||
@ -28,6 +28,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return catalogerName
|
return catalogerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the golang binary cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var pkgs []pkg.Package
|
var pkgs []pkg.Package
|
||||||
|
|||||||
@ -37,6 +37,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return "portage-cataloger"
|
return "portage-cataloger"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the portage cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing portage support files.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing portage support files.
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
dbFileMatches, err := resolver.FilesByGlob(pkg.PortageDBGlob)
|
dbFileMatches, err := resolver.FilesByGlob(pkg.PortageDBGlob)
|
||||||
|
|||||||
@ -33,6 +33,11 @@ func (c *PackageCataloger) Name() string {
|
|||||||
return "python-package-cataloger"
|
return "python-package-cataloger"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the python package cataloger does not use external sources
|
||||||
|
func (c *PackageCataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing python egg and wheel installations.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing python egg and wheel installations.
|
||||||
func (c *PackageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *PackageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var fileMatches []source.Location
|
var fileMatches []source.Location
|
||||||
|
|||||||
@ -27,6 +27,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return catalogerName
|
return catalogerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the rpmdb cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation.
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
fileMatches, err := resolver.FilesByGlob(pkg.RpmDBGlob)
|
fileMatches, err := resolver.FilesByGlob(pkg.RpmDBGlob)
|
||||||
|
|||||||
@ -27,6 +27,11 @@ func (c *Cataloger) Name() string {
|
|||||||
return catalogerName
|
return catalogerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsesExternalSources indicates that the audit binary cataloger does not use external sources
|
||||||
|
func (c *Cataloger) UsesExternalSources() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Catalog identifies executables then attempts to read Rust dependency information from them
|
// Catalog identifies executables then attempts to read Rust dependency information from them
|
||||||
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var pkgs []pkg.Package
|
var pkgs []pkg.Package
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user