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 }