Skip to content

Commit

Permalink
Add containerd support
Browse files Browse the repository at this point in the history
- Add UI handlers (done by @wagoodman)
- Add containerd types and wrappers (done by @wagoodman)
- Add flag for specifying containerd address

Closes anchore#201

Signed-off-by: Shane Dell <shanedell100@gmail.com>
  • Loading branch information
shanedell committed May 8, 2023
1 parent 1ee63c7 commit c9c4011
Show file tree
Hide file tree
Showing 11 changed files with 50 additions and 48 deletions.
2 changes: 1 addition & 1 deletion cmd/syft/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po
checkForApplicationUpdate()
}

return attest.Run(cmd.Context(), app, args)
return attest.Run(cmd.Context(), app, args, ro.ContainerdAddress)
},
}

Expand Down
14 changes: 7 additions & 7 deletions cmd/syft/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/anchore/syft/syft/source"
)

func Run(_ context.Context, app *config.Application, args []string) error {
func Run(_ context.Context, app *config.Application, args []string, containerdAddress string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
Expand All @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
// could be an image or a directory, with or without a scheme
// TODO: validate that source is image
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource, containerdAddress)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
Expand All @@ -62,16 +62,16 @@ func Run(_ context.Context, app *config.Application, args []string) error {
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(app, *si, writer),
execWorker(app, *si, writer, containerdAddress),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error) ([]byte, error) {
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, errs chan error, containerdAddress string) ([]byte, error) {
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions, containerdAddress)
if cleanup != nil {
defer cleanup()
}
Expand All @@ -98,11 +98,11 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
}

//nolint:funlen
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
func execWorker(app *config.Application, si source.Input, writer sbom.Writer, containerdAddress string) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
sBytes, err := buildSBOM(app, si, writer, errs)
sBytes, err := buildSBOM(app, si, writer, errs, containerdAddress)
if err != nil {
errs <- fmt.Errorf("unable to build SBOM: %w", err)
return
Expand Down
8 changes: 5 additions & 3 deletions cmd/syft/cli/options/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
)

type RootOptions struct {
Config string
Quiet bool
Verbose int
Config string
ContainerdAddress string
Quiet bool
Verbose int
}

var _ Interface = (*RootOptions)(nil)
Expand All @@ -18,6 +19,7 @@ func (o *RootOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.PersistentFlags().StringVarP(&o.Config, "config", "c", "", "application config file")
cmd.PersistentFlags().CountVarP(&o.Verbose, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
cmd.PersistentFlags().BoolVarP(&o.Quiet, "quiet", "q", false, "suppress all logging output")
cmd.PersistentFlags().StringVarP(&o.ContainerdAddress, "containerd-address", "a", "", "full path to containerd address")

return bindRootConfigOptions(cmd.PersistentFlags(), v)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/syft/cli/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func Packages(v *viper.Viper, app *config.Application, ro *options.RootOptions,
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return packages.Run(cmd.Context(), app, args)
return packages.Run(cmd.Context(), app, args, ro.ContainerdAddress)
},
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/syft/cli/packages/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/anchore/syft/syft/source"
)

func Run(_ context.Context, app *config.Application, args []string) error {
func Run(_ context.Context, app *config.Application, args []string, containerdAddress string) error {
err := ValidateOutputOptions(app)
if err != nil {
return err
Expand All @@ -42,7 +42,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {

// could be an image or a directory, with or without a scheme
userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource, containerdAddress)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
Expand All @@ -53,20 +53,20 @@ func Run(_ context.Context, app *config.Application, args []string) error {
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(app, *si, writer),
execWorker(app, *si, writer, containerdAddress),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
func execWorker(app *config.Application, si source.Input, writer sbom.Writer, containerdAddress string) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)

src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions, containerdAddress)
if cleanup != nil {
defer cleanup()
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/syft/cli/poweruser.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func PowerUser(v *viper.Viper, app *config.Application, ro *options.RootOptions)
if app.CheckForAppUpdate {
checkForApplicationUpdate()
}
return poweruser.Run(cmd.Context(), app, args)
return poweruser.Run(cmd.Context(), app, args, ro.ContainerdAddress)
},
}

Expand Down
10 changes: 5 additions & 5 deletions cmd/syft/cli/poweruser/poweruser.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/anchore/syft/syft/source"
)

func Run(_ context.Context, app *config.Application, args []string) error {
func Run(_ context.Context, app *config.Application, args []string, containerdAddress string) error {
f := syftjson.Format()
writer, err := sbom.NewWriter(sbom.WriterOption{
Format: f,
Expand All @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
}()

userInput := args[0]
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource, containerdAddress)
if err != nil {
return fmt.Errorf("could not generate source input for packages command: %w", err)
}
Expand All @@ -58,15 +58,15 @@ func Run(_ context.Context, app *config.Application, args []string) error {
subscription := eventBus.Subscribe()

return eventloop.EventLoop(
execWorker(app, *si, writer),
execWorker(app, *si, writer, containerdAddress),
eventloop.SetupSignals(),
subscription,
stereoscope.Cleanup,
ui.Select(options.IsVerbose(app), app.Quiet)...,
)
}

func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
func execWorker(app *config.Application, si source.Input, writer sbom.Writer, containerdAddress string) <-chan error {
errs := make(chan error)
go func() {
defer close(errs)
Expand All @@ -81,7 +81,7 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
return
}

src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions)
src, cleanup, err := source.New(si, app.Registry.ToOptions(), app.Exclusions, containerdAddress)
if err != nil {
errs <- err
return
Expand Down
28 changes: 14 additions & 14 deletions syft/source/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ type Input struct {

// ParseInput generates a source Input that can be used as an argument to generate a new source
// from specific providers including a registry.
func ParseInput(userInput string, platform string) (*Input, error) {
return ParseInputWithName(userInput, platform, "", "")
func ParseInput(userInput string, platform string, containerdAddress string) (*Input, error) {
return ParseInputWithName(userInput, platform, "", "", containerdAddress)
}

// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
// from specific providers including a registry, with an explicit name.
func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) {
func ParseInputWithName(userInput string, platform, name, defaultImageSource string, containerdAddress string) (*Input, error) {
fs := afero.NewOsFs()
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
if err != nil {
Expand All @@ -73,7 +73,7 @@ func ParseInputWithName(userInput string, platform, name, defaultImageSource str
if defaultImageSource != "" {
source = parseDefaultImageSource(defaultImageSource)
} else {
imagePullSource := image.DetermineDefaultImagePullSource(userInput)
imagePullSource := image.DetermineDefaultImagePullSource(userInput, containerdAddress)
source = imagePullSource
}
if location == "" {
Expand Down Expand Up @@ -113,16 +113,16 @@ func parseDefaultImageSource(defaultImageSource string) image.Source {

type sourceDetector func(string) (image.Source, string, error)

func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
source, cleanupFn, err := generateImageSource(in, registryOptions)
func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string, containerdAddress string) (*Source, func(), error) {
source, cleanupFn, err := generateImageSource(in, registryOptions, containerdAddress)
if source != nil {
source.Exclusions = exclusions
}
return source, cleanupFn, err
}

// New produces a Source based on userInput like dir: or image:tag
func New(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
func New(in Input, registryOptions *image.RegistryOptions, exclusions []string, containerdAddress string) (*Source, func(), error) {
var err error
fs := afero.NewOsFs()
var source *Source
Expand All @@ -134,7 +134,7 @@ func New(in Input, registryOptions *image.RegistryOptions, exclusions []string)
case DirectoryScheme:
source, cleanupFn, err = generateDirectorySource(fs, in)
case ImageScheme:
source, cleanupFn, err = generateImageSource(in, registryOptions)
source, cleanupFn, err = generateImageSource(in, registryOptions, containerdAddress)
default:
err = fmt.Errorf("unable to process input for scanning: %q", in.UserInput)
}
Expand All @@ -146,8 +146,8 @@ func New(in Input, registryOptions *image.RegistryOptions, exclusions []string)
return source, cleanupFn, err
}

func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Source, func(), error) {
img, cleanup, err := getImageWithRetryStrategy(in, registryOptions)
func generateImageSource(in Input, registryOptions *image.RegistryOptions, containerdAddress string) (*Source, func(), error) {
img, cleanup, err := getImageWithRetryStrategy(in, registryOptions, containerdAddress)
if err != nil || img == nil {
return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err)
}
Expand All @@ -169,7 +169,7 @@ func parseScheme(userInput string) string {
return parts[0]
}

func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) (*image.Image, func(), error) {
func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions, containerdAddress string) (*image.Image, func(), error) {
ctx := context.TODO()

var opts []stereoscope.Option
Expand All @@ -181,7 +181,7 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions)
opts = append(opts, stereoscope.WithPlatform(in.Platform))
}

img, err := stereoscope.GetImageFromSource(ctx, in.Location, in.ImageSource, opts...)
img, err := stereoscope.GetImageFromSource(ctx, in.Location, in.ImageSource, containerdAddress, opts...)
cleanup := func() {
if err := img.Cleanup(); err != nil {
log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err)
Expand Down Expand Up @@ -215,8 +215,8 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions)

// We need to determine the image source again, such that this determination
// doesn't take scheme parsing into account.
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput, containerdAddress)
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, containerdAddress, opts...)
cleanup = func() {
if err := img.Cleanup(); err != nil {
log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err)
Expand Down
10 changes: 5 additions & 5 deletions syft/source/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestParseInput(t *testing.T) {
if test.errFn == nil {
test.errFn = require.NoError
}
sourceInput, err := ParseInput(test.input, test.platform)
sourceInput, err := ParseInput(test.input, test.platform, "")
test.errFn(t, err)
if test.expected != "" {
require.NotNil(t, sourceInput)
Expand Down Expand Up @@ -596,9 +596,9 @@ func TestDirectoryExclusions(t *testing.T) {
registryOpts := &image.RegistryOptions{}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
sourceInput, err := ParseInput("dir:"+test.input, "")
sourceInput, err := ParseInput("dir:"+test.input, "", "")
require.NoError(t, err)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions, "")
defer fn()

if test.err {
Expand Down Expand Up @@ -696,9 +696,9 @@ func TestImageExclusions(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
sourceInput, err := ParseInput(archiveLocation, "")
sourceInput, err := ParseInput(archiveLocation, "", "")
require.NoError(t, err)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
src, fn, err := New(*sourceInput, registryOpts, test.exclusions, "")
defer fn()

if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions test/integration/catalog_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
userInput := "docker-archive:" + tarPath
sourceInput, err := source.ParseInput(userInput, "")
sourceInput, err := source.ParseInput(userInput, "", "")
require.NoError(b, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil, "")
b.Cleanup(cleanupSource)
if err != nil {
b.Fatalf("unable to get source: %+v", err)
Expand Down
8 changes: 4 additions & 4 deletions test/integration/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
userInput := "docker-archive:" + tarPath
sourceInput, err := source.ParseInput(userInput, "")
sourceInput, err := source.ParseInput(userInput, "", "")
require.NoError(t, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil, "")
t.Cleanup(cleanupSource)
require.NoError(t, err)

Expand Down Expand Up @@ -52,9 +52,9 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco

func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
userInput := "dir:" + dir
sourceInput, err := source.ParseInput(userInput, "")
sourceInput, err := source.ParseInput(userInput, "", "")
require.NoError(t, err)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil, "")
t.Cleanup(cleanupSource)
require.NoError(t, err)

Expand Down

0 comments on commit c9c4011

Please sign in to comment.