finish repository + make syft client

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2020-09-03 18:32:20 -04:00
parent c231a9d7cb
commit 54e0ee4c47
No known key found for this signature in database
GPG Key ID: 86E2870463D5E890
7 changed files with 159 additions and 65 deletions

View File

@ -68,7 +68,17 @@ func startWorker(userInput string) <-chan error {
} }
} }
catalog, scope, distro, err := syft.Catalog(userInput, appConfig.ScopeOpt) config := syft.Config{
PluginDirectory: appConfig.Plugins.Directory,
}
client, err := syft.NewClient(config)
if err != nil {
errs <- fmt.Errorf("could not initialize syft: %+v", err)
return
}
catalog, scope, distro, err := client.Catalog(userInput, appConfig.ScopeOpt)
if err != nil { if err != nil {
errs <- fmt.Errorf("failed to catalog input: %+v", err) errs <- fmt.Errorf("failed to catalog input: %+v", err)
return return

View File

@ -34,6 +34,7 @@ type Cataloger interface {
// TODO: add "IterationNeeded" error to indicate to the driver to continue with another Select/Catalog pass // TODO: add "IterationNeeded" error to indicate to the driver to continue with another Select/Catalog pass
} }
// All returns a slice of all locally defined catalogers (defined in child packages).
func BuiltIn() []Cataloger { func BuiltIn() []Cataloger {
return []Cataloger{ return []Cataloger{
dpkg.New(), dpkg.New(),
@ -46,8 +47,3 @@ func BuiltIn() []Cataloger {
javascript.New(), javascript.New(),
} }
} }
// All returns a slice of all locally defined catalogers (defined in child packages).
func All() []Cataloger {
return BuiltIn()
}

View File

@ -10,13 +10,35 @@ import (
"github.com/anchore/syft/syft/distro" "github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/logger" "github.com/anchore/syft/syft/logger"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/plugin"
"github.com/anchore/syft/syft/scope" "github.com/anchore/syft/syft/scope"
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
) )
type Config struct {
PluginDirectory string
}
type Client struct {
config Config
plugins *plugin.Repository
}
func NewClient(config Config) (Client, error) {
plugins, err := plugin.NewRepositoryFromDirectory(config.PluginDirectory)
if err != nil {
return Client{}, err
}
return Client{
config: config,
plugins: plugins,
}, nil
}
// Catalog the given image from a particular perspective (e.g. squashed scope, all-layers scope). Returns the discovered // Catalog the given image from a particular perspective (e.g. squashed scope, all-layers scope). Returns the discovered
// set of packages, the identified Linux distribution, and the scope object used to wrap the data source. // set of packages, the identified Linux distribution, and the scope object used to wrap the data source.
func Catalog(userInput string, scoptOpt scope.Option) (*pkg.Catalog, *scope.Scope, *distro.Distro, error) { func (c *Client) Catalog(userInput string, scoptOpt scope.Option) (*pkg.Catalog, *scope.Scope, *distro.Distro, error) {
log.Info("cataloging image") log.Info("cataloging image")
s, cleanup, err := scope.NewScope(userInput, scoptOpt) s, cleanup, err := scope.NewScope(userInput, scoptOpt)
defer cleanup() defer cleanup()
@ -24,9 +46,9 @@ func Catalog(userInput string, scoptOpt scope.Option) (*pkg.Catalog, *scope.Scop
return nil, nil, nil, err return nil, nil, nil, err
} }
d := IdentifyDistro(s) d := c.IdentifyDistro(s)
catalog, err := CatalogFromScope(s) catalog, err := c.CatalogFromScope(s)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -36,7 +58,7 @@ func Catalog(userInput string, scoptOpt scope.Option) (*pkg.Catalog, *scope.Scop
// IdentifyDistro attempts to discover what the underlying Linux distribution may be from the available flat files // IdentifyDistro attempts to discover what the underlying Linux distribution may be from the available flat files
// provided by the given scope object. If results are inconclusive a "UnknownDistro" Type is returned. // provided by the given scope object. If results are inconclusive a "UnknownDistro" Type is returned.
func IdentifyDistro(s scope.Scope) distro.Distro { func (c *Client) IdentifyDistro(s scope.Scope) distro.Distro {
d := distro.Identify(s.Resolver) d := distro.Identify(s.Resolver)
if d.Type != distro.UnknownDistroType { if d.Type != distro.UnknownDistroType {
log.Infof("identified distro: %s", d.String()) log.Infof("identified distro: %s", d.String())
@ -47,9 +69,15 @@ func IdentifyDistro(s scope.Scope) distro.Distro {
} }
// Catalog the given scope, which may represent a container image or filesystem. Returns the discovered set of packages. // Catalog the given scope, which may represent a container image or filesystem. Returns the discovered set of packages.
func CatalogFromScope(s scope.Scope) (*pkg.Catalog, error) { func (c *Client) CatalogFromScope(s scope.Scope) (*pkg.Catalog, error) {
log.Info("building the catalog") log.Info("building the catalog")
return cataloger.Catalog(s.Resolver, cataloger.All()...) pluginCatalogers, deactivatePluginsFn, err := c.plugins.ActivateCatalogers()
defer deactivatePluginsFn()
if err != nil {
return nil, err
}
return cataloger.Catalog(s.Resolver, append(pluginCatalogers, cataloger.BuiltIn()...)...)
} }
// SetLogger sets the logger object used for all syft logging calls. // SetLogger sets the logger object used for all syft logging calls.

View File

@ -14,9 +14,7 @@ import (
var _ plugin.GRPCPlugin = &CatalogerPlugin{} var _ plugin.GRPCPlugin = &CatalogerPlugin{}
// Cataloger is the interface exposed as a plugin. // Cataloger is the interface exposed as a plugin.
type Cataloger interface { type Cataloger cataloger.Cataloger
cataloger.Cataloger
}
// CatalogerPlugin is the implementation of plugin.Plugin that is served/consumed. // CatalogerPlugin is the implementation of plugin.Plugin that is served/consumed.
type CatalogerPlugin struct { type CatalogerPlugin struct {

View File

@ -1,7 +0,0 @@
package plugin
// repository
type Collection struct {
byType map[Type]Plugin
byName map[string]Plugin
}

View File

@ -1,43 +0,0 @@
package plugin
import "path/filepath"
func Discover(dir string, pluginTypes ...Type) ([]Plugin, error) {
var err error
if len(pluginTypes) == 0 {
pluginTypes = AllTypes
}
if !filepath.IsAbs(dir) {
dir, err = filepath.Abs(dir)
if err != nil {
return nil, err
}
}
var plugins []Plugin
for _, pluginType := range pluginTypes {
// look into a sub dir named by the plugin type
searchDir := filepath.Join(dir, pluginType.String())
paths, err := filepath.Glob(filepath.Join(searchDir, "*"))
if err != nil {
return nil, err
}
for _, path := range paths {
// TODO: should we use a config for some of this?
plugins = append(plugins, NewPlugin(Config{
// TODO: should the name be org/name instead of just name? this implies changing the dir storage too
Name: filepath.Base(path),
Type: pluginType,
Command: path,
Args: nil, // TODO
Env: nil, // TODO
}))
}
}
return plugins, nil
}

112
syft/plugin/repository.go Normal file
View File

@ -0,0 +1,112 @@
package plugin
import (
"fmt"
"github.com/anchore/syft/syft/cataloger"
"path/filepath"
)
type Repository struct {
byType map[Type][]Plugin
}
func NewRepository() *Repository {
return &Repository{
byType: make(map[Type][]Plugin),
}
}
func NewRepositoryFromDirectory(dir string) (*Repository, error) {
repo := NewRepository()
if dir == "" {
return repo, nil
}
return repo, repo.AddFromDirectory(dir)
}
func (c *Repository) Add(plugin Plugin) {
c.byType[plugin.Config.Type] = append(c.byType[plugin.Config.Type], plugin)
}
func (c *Repository) AddFromDirectory(dir string, pluginTypes ...Type) error {
plugins, err := Discover(dir, pluginTypes...)
if err != nil {
return err
}
for _, plugin := range plugins {
c.Add(plugin)
}
return nil
}
func (c *Repository) Get(pluginType Type) []Plugin {
return c.byType[pluginType]
}
func (c *Repository) ActivateCatalogers() ([]cataloger.Cataloger, func(), error) {
var result []cataloger.Cataloger
var plugins []Plugin
var deactivateFn = func() {
for _, plugin := range plugins {
// TODO: handle error by log
plugin.Stop()
}
}
for _, plugin := range c.Get(TypeCataloger) {
raw, err := plugin.Start()
if err != nil {
return nil, deactivateFn, err
}
plugins = append(plugins, plugin)
theCataloger, ok := raw.(cataloger.Cataloger)
if !ok {
return nil, deactivateFn, fmt.Errorf("activation of cataloger did not return a cataloger object (name=%s)", plugin.Config.Name)
}
result = append(result, theCataloger)
}
return result, deactivateFn, nil
}
func Discover(dir string, pluginTypes ...Type) ([]Plugin, error) {
var err error
if len(pluginTypes) == 0 {
pluginTypes = AllTypes
}
if !filepath.IsAbs(dir) {
dir, err = filepath.Abs(dir)
if err != nil {
return nil, err
}
}
var plugins []Plugin
for _, pluginType := range pluginTypes {
// look into a sub dir named by the plugin type
searchDir := filepath.Join(dir, pluginType.String())
paths, err := filepath.Glob(filepath.Join(searchDir, "*"))
if err != nil {
return nil, err
}
for _, path := range paths {
// TODO: should we use a config for some of this?
plugins = append(plugins, NewPlugin(Config{
// TODO: should the name be org/name instead of just name? this implies changing the dir storage too
Name: filepath.Base(path),
Type: pluginType,
Command: path,
Args: nil, // TODO
Env: nil, // TODO
}))
}
}
return plugins, nil
}