github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/cmd/syft/cli/commands/poweruser.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/gookit/color"
     8  	"github.com/hashicorp/go-multierror"
     9  	"github.com/spf13/cobra"
    10  
    11  	"github.com/anchore/clio"
    12  	"github.com/anchore/stereoscope/pkg/image"
    13  	"github.com/anchore/syft/cmd/syft/cli/eventloop"
    14  	"github.com/anchore/syft/cmd/syft/cli/options"
    15  	"github.com/anchore/syft/internal"
    16  	"github.com/anchore/syft/syft/artifact"
    17  	"github.com/anchore/syft/syft/formats/syftjson"
    18  	"github.com/anchore/syft/syft/sbom"
    19  	"github.com/anchore/syft/syft/source"
    20  )
    21  
    22  const powerUserExample = `  {{.appName}} {{.command}} <image>
    23    DEPRECATED - THIS COMMAND WILL BE REMOVED in v1.0.0
    24    Template outputs are not supported.
    25    All behavior is controlled via application configuration and environment variables (see https://github.com/anchore/syft#configuration)
    26  `
    27  
    28  type powerUserOptions struct {
    29  	options.Config      `yaml:",inline" mapstructure:",squash"`
    30  	options.OutputFile  `yaml:",inline" mapstructure:",squash"`
    31  	options.UpdateCheck `yaml:",inline" mapstructure:",squash"`
    32  	options.Catalog     `yaml:",inline" mapstructure:",squash"`
    33  }
    34  
    35  func PowerUser(app clio.Application) *cobra.Command {
    36  	id := app.ID()
    37  
    38  	pkgs := options.DefaultCatalog()
    39  	pkgs.Secrets.Cataloger.Enabled = true
    40  	pkgs.FileMetadata.Cataloger.Enabled = true
    41  	pkgs.FileContents.Cataloger.Enabled = true
    42  	pkgs.FileClassification.Cataloger.Enabled = true
    43  	opts := &powerUserOptions{
    44  		Catalog: pkgs,
    45  	}
    46  
    47  	return app.SetupCommand(&cobra.Command{
    48  		Use:   "power-user [IMAGE]",
    49  		Short: "Run bulk operations on container images",
    50  		Example: internal.Tprintf(powerUserExample, map[string]interface{}{
    51  			"appName": id.Name,
    52  			"command": "power-user",
    53  		}),
    54  		Args:    validatePackagesArgs,
    55  		Hidden:  true,
    56  		PreRunE: applicationUpdateCheck(id, &opts.UpdateCheck),
    57  		RunE: func(cmd *cobra.Command, args []string) error {
    58  			return runPowerUser(id, opts, args[0])
    59  		},
    60  	}, opts)
    61  }
    62  
    63  //nolint:funlen
    64  func runPowerUser(id clio.Identification, opts *powerUserOptions, userInput string) error {
    65  	writer, err := opts.SBOMWriter(syftjson.Format())
    66  	if err != nil {
    67  		return err
    68  	}
    69  	defer func() {
    70  		// inform user at end of run that command will be removed
    71  		deprecated := color.Style{color.Red, color.OpBold}.Sprint("DEPRECATED: This command will be removed in v1.0.0")
    72  		fmt.Fprintln(os.Stderr, deprecated)
    73  	}()
    74  
    75  	tasks, err := eventloop.Tasks(&opts.Catalog)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	detection, err := source.Detect(
    81  		userInput,
    82  		source.DetectConfig{
    83  			DefaultImageSource: opts.DefaultImagePullSource,
    84  		},
    85  	)
    86  	if err != nil {
    87  		return fmt.Errorf("could not deteremine source: %w", err)
    88  	}
    89  
    90  	var platform *image.Platform
    91  
    92  	if opts.Platform != "" {
    93  		platform, err = image.NewPlatform(opts.Platform)
    94  		if err != nil {
    95  			return fmt.Errorf("invalid platform: %w", err)
    96  		}
    97  	}
    98  
    99  	src, err := detection.NewSource(
   100  		source.DetectionSourceConfig{
   101  			Alias: source.Alias{
   102  				Name:    opts.Source.Name,
   103  				Version: opts.Source.Version,
   104  			},
   105  			RegistryOptions: opts.Registry.ToOptions(),
   106  			Platform:        platform,
   107  			Exclude: source.ExcludeConfig{
   108  				Paths: opts.Exclusions,
   109  			},
   110  			DigestAlgorithms: nil,
   111  			BasePath:         opts.BasePath,
   112  		},
   113  	)
   114  
   115  	if src != nil {
   116  		defer src.Close()
   117  	}
   118  	if err != nil {
   119  		return fmt.Errorf("failed to construct source from user input %q: %w", userInput, err)
   120  	}
   121  
   122  	s := sbom.SBOM{
   123  		Source: src.Describe(),
   124  		Descriptor: sbom.Descriptor{
   125  			Name:          id.Name,
   126  			Version:       id.Version,
   127  			Configuration: opts,
   128  		},
   129  	}
   130  
   131  	var errs error
   132  	var relationships []<-chan artifact.Relationship
   133  	for _, task := range tasks {
   134  		c := make(chan artifact.Relationship)
   135  		relationships = append(relationships, c)
   136  
   137  		go func(task eventloop.Task) {
   138  			err := eventloop.RunTask(task, &s.Artifacts, src, c)
   139  			errs = multierror.Append(errs, err)
   140  		}(task)
   141  	}
   142  
   143  	if errs != nil {
   144  		return errs
   145  	}
   146  
   147  	s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
   148  
   149  	if err := writer.Write(s); err != nil {
   150  		return fmt.Errorf("failed to write sbom: %w", err)
   151  	}
   152  
   153  	return nil
   154  }