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  }