go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/cnquery/cmd/plugin.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package cmd 5 6 import ( 7 "os" 8 9 "github.com/cockroachdb/errors" 10 "github.com/hashicorp/go-plugin" 11 "github.com/rs/zerolog/log" 12 "github.com/spf13/cobra" 13 "go.mondoo.com/cnquery/cli/config" 14 "go.mondoo.com/cnquery/cli/printer" 15 "go.mondoo.com/cnquery/cli/reporter" 16 "go.mondoo.com/cnquery/cli/shell" 17 "go.mondoo.com/cnquery/llx" 18 "go.mondoo.com/cnquery/logger" 19 "go.mondoo.com/cnquery/mqlc" 20 "go.mondoo.com/cnquery/mqlc/parser" 21 "go.mondoo.com/cnquery/providers" 22 pp "go.mondoo.com/cnquery/providers-sdk/v1/plugin" 23 "go.mondoo.com/cnquery/providers-sdk/v1/upstream" 24 "go.mondoo.com/cnquery/shared" 25 run "go.mondoo.com/cnquery/shared/proto" 26 "google.golang.org/protobuf/proto" 27 ) 28 29 // pluginCmd represents the version command 30 var pluginCmd = &cobra.Command{ 31 Use: "run_as_plugin", 32 Hidden: true, 33 Short: "Run as a plugin.", 34 Run: func(cmd *cobra.Command, args []string) { 35 plugin.Serve(&plugin.ServeConfig{ 36 HandshakeConfig: shared.Handshake, 37 Plugins: map[string]plugin.Plugin{ 38 "counter": &shared.CNQueryPlugin{Impl: &cnqueryPlugin{}}, 39 }, 40 41 // A non-nil value here enables gRPC serving for this plugin... 42 GRPCServer: plugin.DefaultGRPCServer, 43 }) 44 }, 45 } 46 47 func init() { 48 rootCmd.AddCommand(pluginCmd) 49 } 50 51 type cnqueryPlugin struct{} 52 53 func (c *cnqueryPlugin) RunQuery(conf *run.RunQueryConfig, runtime *providers.Runtime, out shared.OutputHelper) error { 54 if conf.Command == "" && conf.Input == "" { 55 return errors.New("No command provided, nothing to do.") 56 } 57 58 opts, optsErr := config.Read() 59 if optsErr != nil { 60 log.Fatal().Err(optsErr).Msg("could not load configuration") 61 } 62 63 config.DisplayUsedConfig() 64 65 if conf.DoParse { 66 ast, err := parser.Parse(conf.Command) 67 if err != nil { 68 return errors.Wrap(err, "failed to parse command") 69 } 70 out.WriteString(logger.PrettyJSON(ast)) 71 return nil 72 } 73 74 if conf.DoAst { 75 b, err := mqlc.Compile(conf.Command, nil, mqlc.NewConfig(runtime.Schema(), conf.Features)) 76 if err != nil { 77 return errors.Wrap(err, "failed to compile command") 78 } 79 80 out.WriteString(logger.PrettyJSON((b)) + "\n" + printer.DefaultPrinter.CodeBundle(b)) 81 return nil 82 } 83 84 var upstreamConfig *upstream.UpstreamConfig 85 serviceAccount := opts.GetServiceCredential() 86 if serviceAccount != nil { 87 upstreamConfig = &upstream.UpstreamConfig{ 88 SpaceMrn: opts.GetParentMrn(), 89 ApiEndpoint: opts.UpstreamApiEndpoint(), 90 Incognito: conf.Incognito, 91 Creds: serviceAccount, 92 } 93 } 94 95 err := runtime.Connect(&pp.ConnectReq{ 96 Features: config.Features, 97 Asset: conf.Inventory.Spec.Assets[0], 98 Upstream: upstreamConfig, 99 }) 100 if err != nil { 101 return err 102 } 103 104 if conf.Format == "json" { 105 out.WriteString("[") 106 } 107 108 // FIXME: workaround for gcp-snapshot 109 // For a gcp-snapshot asset, we start with a GCP connection. 110 // This get's overridden by a filesystem connection. The filesystem connection is what we need for the scan 111 // But later, we need the GCP runtime to cleanup the snapshot disk 112 if runtime.Provider.Instance.Name == "gcp" && runtime.Provider.Connection.Name == "filesystem" { 113 defer runtime.Close() 114 } 115 116 assets, err := providers.ProcessAssetCandidates(runtime, runtime.Provider.Connection, upstreamConfig, conf.PlatformId) 117 if err != nil { 118 return err 119 } 120 121 for i := range assets { 122 connectAsset := assets[i] 123 connectAssetRuntime, err := providers.Coordinator.RuntimeFor(connectAsset, runtime) 124 if err != nil { 125 return err 126 } 127 128 err = connectAssetRuntime.Connect(&pp.ConnectReq{ 129 Features: config.Features, 130 Asset: connectAsset, 131 Upstream: upstreamConfig, 132 }) 133 if err != nil { 134 return err 135 } 136 137 // when we close the shell, we need to close the backend and store the recording 138 onCloseHandler := func() { 139 // FIXME: store recording 140 // m.StoreRecording(viper.GetString("record-file")) 141 } 142 143 shellOptions := []shell.ShellOption{} 144 shellOptions = append(shellOptions, shell.WithOnCloseListener(onCloseHandler)) 145 shellOptions = append(shellOptions, shell.WithFeatures(conf.Features)) 146 shellOptions = append(shellOptions, shell.WithOutput(out)) 147 148 if upstreamConfig != nil { 149 shellOptions = append(shellOptions, shell.WithUpstreamConfig(upstreamConfig)) 150 } 151 152 sh, err := shell.New(connectAssetRuntime, shellOptions...) 153 if err != nil { 154 return errors.Wrap(err, "failed to initialize the shell") 155 } 156 defer func() { 157 // prevent the recording from being closed multiple times 158 connectAssetRuntime.Recording = providers.NullRecording{} 159 sh.Close() 160 }() 161 162 var code *llx.CodeBundle 163 var results map[string]*llx.RawResult 164 if conf.Input != "" { 165 var raw []byte 166 raw, err = os.ReadFile(conf.Input) 167 if err != nil { 168 return errors.Wrap(err, "failed to read code bundle from file") 169 } 170 var b llx.CodeBundle 171 if err = proto.Unmarshal(raw, &b); err != nil { 172 return errors.Wrap(err, "failed to unmarshal code bundle") 173 } 174 code = &b 175 results, err = sh.RunOnceBundle(code) 176 } else { 177 code, results, err = sh.RunOnce(conf.Command) 178 } 179 if err != nil { 180 return errors.Wrap(err, "failed to run") 181 } 182 183 if conf.Format == "llx" && conf.Output != "" { 184 out, err := proto.Marshal(code) 185 if err != nil { 186 return errors.Wrap(err, "failed to marshal code bundle") 187 } 188 err = os.WriteFile(conf.Output, out, 0o644) 189 if err != nil { 190 return errors.Wrap(err, "failed to save code bundle") 191 } 192 return nil 193 } 194 195 if conf.Format != "json" { 196 sh.PrintResults(code, results) 197 } else { 198 reporter.BundleResultsToJSON(code, results, out) 199 if len(assets) != i+1 { 200 out.WriteString(",") 201 } 202 } 203 204 } 205 206 if conf.Format == "json" { 207 out.WriteString("]") 208 } 209 210 return nil 211 }