go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/reporter/csv.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package reporter
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/csv"
     9  
    10  	"go.mondoo.com/cnquery/explorer"
    11  	"go.mondoo.com/cnquery/llx"
    12  	"go.mondoo.com/cnquery/mrn"
    13  	"go.mondoo.com/cnquery/shared"
    14  )
    15  
    16  type csvStruct struct {
    17  	AssetMrn    string
    18  	AssetId     string
    19  	AssetName   string
    20  	QueryMrn    string
    21  	QueryTitle  string
    22  	MQL         string
    23  	QueryResult string
    24  }
    25  
    26  func (c csvStruct) toSlice() []string {
    27  	return []string{c.AssetMrn, c.AssetId, c.AssetName, c.QueryMrn, c.QueryTitle, c.MQL, c.QueryResult}
    28  }
    29  
    30  // ReportCollectionToCSV writes the given report collection to the given output directory
    31  func ReportCollectionToCSV(data *explorer.ReportCollection, out shared.OutputHelper) error {
    32  	w := csv.NewWriter(out)
    33  
    34  	// write header
    35  	err := w.Write(csvStruct{
    36  		"Asset Mrn",
    37  		"Asset ID",
    38  		"Asset Name",
    39  		"Query Mrn",
    40  		"Query Title",
    41  		"MQL",
    42  		"Query Result",
    43  	}.toSlice())
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	queryMrnIdx := map[string]*explorer.Mquery{}
    49  	if data.Bundle != nil {
    50  		for i := range data.Bundle.Packs {
    51  			pack := data.Bundle.Packs[i]
    52  			for j := range pack.Queries {
    53  				query := pack.Queries[j]
    54  				queryMrnIdx[query.CodeId] = query
    55  			}
    56  		}
    57  	}
    58  
    59  	for i := range data.Assets {
    60  		asset := data.Assets[i]
    61  		parsedMrn, err := mrn.NewMRN(asset.Mrn)
    62  		if err != nil {
    63  			return err
    64  		}
    65  		assetId, err := parsedMrn.ResourceID("assets")
    66  		if err != nil {
    67  			return err
    68  		}
    69  
    70  		if data.Errors != nil {
    71  			errStatus, ok := data.Errors[asset.Mrn]
    72  			if ok {
    73  				err := w.Write(csvStruct{
    74  					AssetMrn:    asset.Mrn,
    75  					AssetId:     assetId,
    76  					AssetName:   asset.Name,
    77  					QueryMrn:    "",
    78  					QueryTitle:  "",
    79  					MQL:         "",
    80  					QueryResult: errStatus.Message,
    81  				}.toSlice())
    82  				if err != nil {
    83  					return err
    84  				}
    85  			}
    86  		}
    87  
    88  		if data.Reports != nil {
    89  			report, ok := data.Reports[asset.Mrn]
    90  			if ok {
    91  				results := report.RawResults()
    92  				resolvedPack := data.Resolved[asset.Mrn]
    93  				if resolvedPack != nil && resolvedPack.ExecutionJob != nil {
    94  					for qid, query := range resolvedPack.ExecutionJob.Queries {
    95  						buf := &bytes.Buffer{}
    96  						resultWriter := &shared.IOWriter{Writer: buf}
    97  						err := ResultsToCsvEntry(query.Code, results, resultWriter)
    98  						if err != nil {
    99  							return err
   100  						}
   101  
   102  						var queryMrn string
   103  						var queryTitle string
   104  						mQuery := queryMrnIdx[qid]
   105  						if mQuery != nil {
   106  							queryMrn = mQuery.Mrn
   107  							queryTitle = mQuery.Title
   108  						}
   109  
   110  						entry := csvStruct{
   111  							AssetMrn:    asset.Mrn,
   112  							AssetId:     assetId,
   113  							AssetName:   asset.Name,
   114  							QueryMrn:    queryMrn,
   115  							QueryTitle:  queryTitle,
   116  							MQL:         query.Query,
   117  							QueryResult: string(buf.Bytes()),
   118  						}
   119  
   120  						err = w.Write(entry.toSlice())
   121  						if err != nil {
   122  							return err
   123  						}
   124  					}
   125  				}
   126  			}
   127  		}
   128  	}
   129  
   130  	w.Flush()
   131  	return w.Error()
   132  }
   133  
   134  func ResultsToCsvEntry(code *llx.CodeBundle, results map[string]*llx.RawResult, out shared.OutputHelper) error {
   135  	var checksums []string
   136  	eps := code.CodeV2.Entrypoints()
   137  	checksums = make([]string, len(eps))
   138  	for i, ref := range eps {
   139  		checksums[i] = code.CodeV2.Checksums[ref]
   140  	}
   141  
   142  	// We try to flatten the information as much as possible. If we have multiple checksums we have no choice but to use
   143  	// a full json out, otherwise we can use a simple value without the labels.
   144  	if len(checksums) == 1 {
   145  		checksum := checksums[0]
   146  		result := results[checksum]
   147  		if result != nil {
   148  			jsonData := result.Data.JSON(checksum, code)
   149  			out.Write(jsonData)
   150  		}
   151  		return nil
   152  	}
   153  
   154  	return BundleResultsToJSON(code, results, out)
   155  }