go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/inventoryloader/inventory.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package inventoryloader 5 6 import ( 7 "bytes" 8 "io" 9 "os" 10 "runtime" 11 12 "github.com/cockroachdb/errors" 13 "github.com/rs/zerolog/log" 14 "github.com/spf13/viper" 15 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 16 "go.mondoo.com/cnquery/providers-sdk/v1/inventory/ansibleinventory" 17 "go.mondoo.com/cnquery/providers-sdk/v1/inventory/domainlist" 18 ) 19 20 func loadDataPipe() ([]byte, bool) { 21 isTerminal := true 22 isNamedPipe := false 23 switch runtime.GOOS { 24 case "darwin", "dragonfly", "netbsd", "solaris", "linux": 25 // when we run the following command, the detection differs between macos and linux 26 // cat options.json | mondoo scan 27 // for macos, we get isNamedPipe=false, isTerminal=false, size > 0 28 // but this only applies to direct terminal execution, for the same command in a bash file, we get 29 // for macos bash script, we get isNamedPipe=true, isTerminal=false, size > 0 30 // for linux, we get isNamedPipe=true, isTerminal=false, size=0 31 // Therefore we always want to check for file size if we detected its not a terminal 32 // If we are not checking for fi.Size() > 0 even a run inside of a bash script turn out 33 // to be pipes, therefore we need to verify that there is some data available at the pipe 34 // also read https://flaviocopes.com/go-shell-pipes/ 35 fi, _ := os.Stdin.Stat() 36 isTerminal = (fi.Mode() & os.ModeCharDevice) == os.ModeCharDevice 37 isNamedPipe = (fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe 38 log.Debug().Bool("isTerminal", isTerminal).Bool("isNamedPipe", isNamedPipe).Int64("size", fi.Size()).Msg("check if we got the scan config from pipe") 39 if isNamedPipe || (!isTerminal && fi.Size() > 0) { 40 // Pipe input 41 log.Debug().Msg("read scan config from stdin pipe") 42 43 // read stdin into buffer 44 data, err := io.ReadAll(os.Stdin) 45 if err != nil { 46 log.Error().Err(err).Msg("could not read from pipe") 47 return nil, false 48 } 49 return data, true 50 } 51 } 52 return nil, false 53 } 54 55 // Parse uses the viper flags for `--inventory-file` to load the inventory 56 func Parse() (*inventory.Inventory, error) { 57 inventoryFilePath := viper.GetString("inventory-file") 58 59 // check in an inventory file was provided 60 if inventoryFilePath == "" { 61 return inventory.New(), nil 62 } 63 64 var data []byte 65 var err error 66 67 // for we just read the data from the input file 68 if inventoryFilePath != "-" { 69 log.Info().Str("inventory-file", inventoryFilePath).Msg("load inventory") 70 data, err = os.ReadFile(inventoryFilePath) 71 if err != nil { 72 return nil, err 73 } 74 } else { 75 log.Info().Msg("load inventory from piped input") 76 var ok bool 77 data, ok = loadDataPipe() 78 if !ok { 79 return nil, errors.New("could not read inventory from piped input") 80 } 81 } 82 83 // force detection 84 if viper.GetBool("inventory-ansible") { 85 log.Debug().Msg("parse ansible inventory") 86 inventory, err := parseAnsibleInventory(data) 87 if err != nil { 88 return nil, err 89 } 90 return inventory, nil 91 } 92 93 if viper.GetBool("inventory-domainlist") { 94 log.Debug().Msg("parse domainlist inventory") 95 inventory, err := parseDomainListInventory(data) 96 if err != nil { 97 return nil, err 98 } 99 return inventory, nil 100 } 101 102 // load mondoo inventory 103 log.Debug().Msg("parse inventory") 104 res, err := inventory.InventoryFromYAML(data) 105 if err != nil { 106 return nil, err 107 } 108 // we preprocess the content here, to ensure relative paths are 109 if res.Metadata.Labels == nil { 110 res.Metadata.Labels = map[string]string{} 111 } 112 res.Metadata.Labels[inventory.InventoryFilePath] = inventoryFilePath 113 err = res.PreProcess() 114 if err != nil { 115 return nil, err 116 } 117 118 return res, nil 119 } 120 121 func parseAnsibleInventory(data []byte) (*inventory.Inventory, error) { 122 inventory, err := ansibleinventory.Parse(data) 123 if err != nil { 124 return nil, err 125 } 126 return inventory.ToV1Inventory(), nil 127 } 128 129 func parseDomainListInventory(data []byte) (*inventory.Inventory, error) { 130 inventory, err := domainlist.Parse(bytes.NewReader(data)) 131 if err != nil { 132 return nil, err 133 } 134 return inventory.ToV1Inventory(), nil 135 } 136 137 // ParseOrUse tries to load the inventory and if nothing exists it 138 // will instead use the provided asset. 139 func ParseOrUse(cliAsset *inventory.Asset, insecure bool) (*inventory.Inventory, error) { 140 var v1inventory *inventory.Inventory 141 var err error 142 143 // parses optional inventory file if inventory was not piped already 144 v1inventory, err = Parse() 145 if err != nil { 146 return nil, errors.Wrap(err, "could not parse inventory") 147 } 148 149 // add asset from cli to inventory 150 if (len(v1inventory.Spec.GetAssets()) == 0) && cliAsset != nil { 151 v1inventory.AddAssets(cliAsset) 152 } 153 154 // if the --insecure flag is set, we overwrite the individual setting for the asset 155 if insecure == true { 156 v1inventory.MarkConnectionsInsecure() 157 } 158 159 return v1inventory, nil 160 }