github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/cli.go (about)

     1  package cli
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"reflect"
     7  
     8  	"github.com/anchore/syft/cmd/syft/cli/options"
     9  	cranecmd "github.com/google/go-containerregistry/cmd/crane/cmd"
    10  	"github.com/spf13/cobra"
    11  
    12  	"github.com/anchore/clio"
    13  	"github.com/anchore/stereoscope"
    14  	"github.com/anchore/syft/cmd/syft/cli/commands"
    15  	handler "github.com/anchore/syft/cmd/syft/cli/ui"
    16  	"github.com/lineaje-labs/syft/cmd/syft/internal/ui"
    17  	"github.com/lineaje-labs/syft/internal/bus"
    18  	"github.com/lineaje-labs/syft/internal/log"
    19  	"github.com/lineaje-labs/syft/internal/redact"
    20  )
    21  
    22  // Application constructs the `syft packages` command and aliases the root command to `syft packages`.
    23  // It is also responsible for organizing flag usage and injecting the application config for each command.
    24  // It also constructs the syft attest command and the syft version command.
    25  // `RunE` is the earliest that the complete application configuration can be loaded.
    26  func Application(id clio.Identification) clio.Application {
    27  	app, _ := create(id, os.Stdout)
    28  	return app
    29  }
    30  
    31  // Command returns the root command for the syft CLI application. This is useful for embedding the entire syft CLI
    32  // into an existing application.
    33  func Command(id clio.Identification) *cobra.Command {
    34  	_, cmd := create(id, os.Stdout)
    35  	return cmd
    36  }
    37  
    38  func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Command) {
    39  	clioCfg := clio.NewSetupConfig(id).
    40  		WithGlobalConfigFlag().   // add persistent -c <path> for reading an application config from
    41  		WithGlobalLoggingFlags(). // add persistent -v and -q flags tied to the logging config
    42  		WithConfigInRootHelp().   // --help on the root command renders the full application config in the help text
    43  		WithUIConstructor(
    44  			// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
    45  			func(cfg clio.Config) ([]clio.UI, error) {
    46  				noUI := ui.None(out, cfg.Log.Quiet)
    47  				if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
    48  					return []clio.UI{noUI}, nil
    49  				}
    50  
    51  				return []clio.UI{
    52  					ui.New(out, cfg.Log.Quiet,
    53  						handler.New(handler.DefaultHandlerConfig()),
    54  					),
    55  					noUI,
    56  				}, nil
    57  			},
    58  		).
    59  		WithInitializers(
    60  			func(state *clio.State) error {
    61  				// clio is setting up and providing the bus, redact store, and logger to the application. Once loaded,
    62  				// we can hoist them into the internal packages for global use.
    63  				stereoscope.SetBus(state.Bus)
    64  				bus.Set(state.Bus)
    65  
    66  				redact.Set(state.RedactStore)
    67  
    68  				log.Set(state.Logger)
    69  				stereoscope.SetLogger(state.Logger)
    70  
    71  				return nil
    72  			},
    73  		).
    74  		WithPostRuns(func(state *clio.State, err error) {
    75  			// Do not run cleanup if it is disabled
    76  			if !isCleanupDisabled(state) {
    77  				stereoscope.Cleanup()
    78  			}
    79  		})
    80  
    81  	app := clio.New(*clioCfg)
    82  
    83  	// since root is aliased as the packages cmd we need to construct this command first
    84  	// we also need the command to have information about the `root` options because of this alias
    85  	packagesCmd := commands.Packages(app)
    86  
    87  	// rootCmd is currently an alias for the packages command
    88  	rootCmd := commands.Root(app, packagesCmd)
    89  
    90  	// add sub-commands
    91  	rootCmd.AddCommand(
    92  		packagesCmd,
    93  		commands.Attest(app),
    94  		commands.Convert(app),
    95  		clio.VersionCommand(id),
    96  		cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
    97  	)
    98  
    99  	// explicitly set Cobra output to the real stdout to write things like errors and help
   100  	rootCmd.SetOut(out)
   101  
   102  	return app, rootCmd
   103  }
   104  
   105  // isCleanupDisabled checks if the cleanup option is disabled in the provided state object.
   106  // This option is unexported so reflection is used to get the value set.
   107  func isCleanupDisabled(state *clio.State) bool {
   108  	var cleanupDisabled bool
   109  	for _, configObj := range state.Config.FromCommands {
   110  		if reflect.TypeOf(configObj).String() == "*commands.packagesOptions" { // Cleanup option is part of packageOptions
   111  			configObjData := reflect.ValueOf(configObj)
   112  			if configObjData.Kind() == reflect.Ptr && configObjData.Elem().Kind() == reflect.Struct {
   113  				configObjData = configObjData.Elem()
   114  				for i := 0; i < configObjData.NumField(); i++ {
   115  					field := configObjData.Field(i)
   116  					if field.Type().Name() == "Catalog" { // Cleanup option is part of Catalog
   117  						catalogData, ok := field.Interface().(options.Catalog)
   118  						if ok {
   119  							cleanupDisabled = catalogData.CleanupDisabled
   120  						}
   121  						break
   122  					}
   123  				}
   124  				break
   125  			}
   126  		}
   127  	}
   128  	return cleanupDisabled
   129  }