go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/cnquery/cmd/root.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package cmd 5 6 import ( 7 "net/http" 8 "os" 9 "regexp" 10 "strings" 11 12 "github.com/rs/zerolog" 13 "github.com/rs/zerolog/log" 14 "github.com/spf13/cobra" 15 "github.com/spf13/cobra/doc" 16 "github.com/spf13/viper" 17 "go.mondoo.com/cnquery" 18 "go.mondoo.com/cnquery/cli/config" 19 "go.mondoo.com/cnquery/cli/providers" 20 "go.mondoo.com/cnquery/cli/sysinfo" 21 "go.mondoo.com/cnquery/cli/theme" 22 "go.mondoo.com/cnquery/logger" 23 "go.mondoo.com/ranger-rpc" 24 "go.mondoo.com/ranger-rpc/plugins/scope" 25 ) 26 27 const ( 28 askForPasswordValue = ">passwordisnotset<" 29 rootCmdDesc = "cnquery is a cloud-native tool for querying your entire fleet.\n" 30 31 // we send a 78 exit code to prevent systemd service from restart 32 ConfigurationErrorCode = 78 33 ) 34 35 // rootCmd represents the base command when called without any subcommands 36 var rootCmd = &cobra.Command{ 37 Use: "cnquery", 38 Short: "cnquery CLI", 39 Long: theme.DefaultTheme.Landing + "\n\n" + rootCmdDesc, 40 PersistentPreRun: func(cmd *cobra.Command, args []string) { 41 initLogger(cmd) 42 }, 43 } 44 45 // Execute adds all child commands to the root command and sets flags appropriately. 46 // This is called by main.main(). It only needs to happen once to the rootCmd. 47 func Execute() { 48 err := providers.AttachCLIs( 49 rootCmd, 50 &providers.Command{ 51 Command: shellCmd, 52 Run: shellRun, 53 Action: "Interactive shell with ", 54 }, 55 &providers.Command{ 56 Command: RunCmd, 57 Run: RunCmdRun, 58 Action: "Run a query with ", 59 }, 60 &providers.Command{ 61 Command: scanCmd, 62 Run: scanCmdRun, 63 Action: "Scan ", 64 }, 65 ) 66 if err != nil { 67 log.Error().Msg(err.Error()) 68 os.Exit(1) 69 } 70 71 if err := rootCmd.Execute(); err != nil { 72 log.Error().Msg(err.Error()) 73 os.Exit(1) 74 } 75 } 76 77 func init() { 78 // NOTE: we need to call this super early, otherwise the CLI color output on Windows is broken for the first lines 79 // since the log instance is already initialized, replace default zerolog color output with our own 80 // use color logger by default 81 logger.CliCompactLogger(logger.LogOutputWriter) 82 zerolog.SetGlobalLevel(zerolog.InfoLevel) 83 84 // TODO harmonize with initLogger, which is called later and attached to the command 85 // here we set the log level only by environment variable 86 envLevel, ok := logger.GetEnvLogLevel() 87 if ok { 88 logger.Set(envLevel) 89 } 90 91 config.DefaultConfigFile = "mondoo.yml" 92 93 rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Enable verbose output") 94 rootCmd.PersistentFlags().String("log-level", "info", "Set log level: error, warn, info, debug, trace") 95 rootCmd.PersistentFlags().String("api-proxy", "", "Set proxy for communications with Mondoo API") 96 rootCmd.PersistentFlags().Bool("auto-update", true, "Enable automatic provider installation and update") 97 viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose")) 98 viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level")) 99 viper.BindPFlag("api_proxy", rootCmd.PersistentFlags().Lookup("api-proxy")) 100 viper.BindPFlag("auto_update", rootCmd.PersistentFlags().Lookup("auto-update")) 101 viper.BindEnv("features") 102 103 config.Init(rootCmd) 104 } 105 106 func initLogger(cmd *cobra.Command) { 107 // environment variables always over-write custom flags 108 envLevel, ok := logger.GetEnvLogLevel() 109 if ok { 110 logger.Set(envLevel) 111 return 112 } 113 114 // retrieve log-level from flags 115 level := viper.GetString("log-level") 116 if v := viper.GetBool("verbose"); v { 117 level = "debug" 118 } 119 logger.Set(level) 120 } 121 122 func defaultRangerPlugins(sysInfo *sysinfo.SystemInfo, features cnquery.Features) []ranger.ClientPlugin { 123 plugins := []ranger.ClientPlugin{} 124 plugins = append(plugins, scope.NewRequestIDRangerPlugin()) 125 plugins = append(plugins, sysInfoHeader(sysInfo, features)) 126 return plugins 127 } 128 129 func sysInfoHeader(sysInfo *sysinfo.SystemInfo, features cnquery.Features) ranger.ClientPlugin { 130 const ( 131 HttpHeaderUserAgent = "User-Agent" 132 HttpHeaderClientFeatures = "Mondoo-Features" 133 HttpHeaderPlatformID = "Mondoo-PlatformID" 134 ) 135 136 h := http.Header{} 137 info := map[string]string{ 138 "cnquery": cnquery.Version, 139 "build": cnquery.Build, 140 } 141 if sysInfo != nil { 142 info["PN"] = sysInfo.Platform.Name 143 info["PR"] = sysInfo.Platform.Version 144 info["PA"] = sysInfo.Platform.Arch 145 info["IP"] = sysInfo.IP 146 info["HN"] = sysInfo.Hostname 147 h.Set(HttpHeaderPlatformID, sysInfo.PlatformId) 148 } 149 h.Set(HttpHeaderUserAgent, scope.XInfoHeader(info)) 150 h.Set(HttpHeaderClientFeatures, features.Encode()) 151 return scope.NewCustomHeaderRangerPlugin(h) 152 } 153 154 var reMdName = regexp.MustCompile(`/([^/]+)\.md$`) 155 156 func GenerateMarkdown(dir string) error { 157 rootCmd.DisableAutoGenTag = true 158 159 // We need to remove our fancy logo from the markdown output, 160 // since it messes with the formatting. 161 rootCmd.Long = rootCmdDesc 162 163 files := []string{} 164 err := doc.GenMarkdownTreeCustom(rootCmd, dir, func(s string) string { 165 files = append(files, s) 166 167 titles := reMdName.FindStringSubmatch(s) 168 if len(titles) == 0 { 169 return "" 170 } 171 title := strings.ReplaceAll(titles[1], "_", " ") 172 173 return "---\n" + 174 "id: " + titles[1] + "\n" + 175 "title: " + title + "\n" + 176 "---\n\n" 177 }, func(s string) string { return s }) 178 if err != nil { 179 return err 180 } 181 182 // we need to remove the first headline, since it is doubled with the 183 // headline from the ID. Really annoying, all this needs a rewrite. 184 for i := range files { 185 file := files[i] 186 raw, err := os.ReadFile(file) 187 if err != nil { 188 return err 189 } 190 191 if !strings.HasPrefix(string(raw), "---\nid:") { 192 continue 193 } 194 195 start := strings.Index(string(raw), "\n## ") 196 if start < 0 { 197 continue 198 } 199 200 end := start 201 for i := start + 3; i < len(raw); i++ { 202 if raw[i] == '\n' { 203 end = i 204 break 205 } 206 } 207 208 res := append(raw[0:start], raw[end:]...) 209 err = os.WriteFile(file, res, 0o644) 210 if err != nil { 211 return err 212 } 213 } 214 215 return nil 216 }