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 }