github.com/rainforestapp/rainforest-cli@v2.12.0+incompatible/tabularvars.go (about) 1 package main 2 3 import ( 4 "math" 5 "os" 6 7 "encoding/csv" 8 9 "errors" 10 11 "strings" 12 13 "log" 14 15 "github.com/rainforestapp/rainforest-cli/rainforest" 16 "github.com/urfave/cli" 17 ) 18 19 // tabularVariablesAPI is part of the API connected to the tabular variables 20 type tabularVariablesAPI interface { 21 GetGenerators() ([]rainforest.Generator, error) 22 DeleteGenerator(genID int) error 23 CreateTabularVar(name, description string, 24 columns []string, singleUse bool) (*rainforest.Generator, error) 25 AddGeneratorRowsFromTable(targetGenerator *rainforest.Generator, 26 targetColumns []string, rowData [][]string) error 27 } 28 29 // uploadTabularVar takes a path to csv file and creates tabular variable generator from it. 30 func uploadTabularVar(api tabularVariablesAPI, pathToCSV, name string, overwrite, singleUse bool) error { 31 // Open up the CSV file and parse it, return early with an error if we fail to get to the file 32 f, err := os.Open(pathToCSV) 33 if err != nil { 34 return err 35 } 36 defer f.Close() 37 38 r := csv.NewReader(f) 39 records, err := r.ReadAll() 40 if err != nil { 41 return err 42 } 43 44 // Check if the variable exists in RF 45 var existingGenID int 46 generators, err := api.GetGenerators() 47 if err != nil { 48 return err 49 } 50 51 for _, gen := range generators { 52 if gen.Name == name { 53 existingGenID = gen.ID 54 } 55 } 56 57 if existingGenID != 0 { 58 if overwrite { 59 // if variable exists and we want to override it with new one delete it here 60 log.Printf("Tabular var %v exists, overwriting it with new data.\n", name) 61 err = api.DeleteGenerator(existingGenID) 62 if err != nil { 63 return err 64 } 65 } else { 66 // if variable exists but we didn't specify to override it then return with error 67 return errors.New("Tabular variable: " + name + 68 " already exists, use different name or choose an option to override it") 69 } 70 } 71 72 // prepare input data 73 columnNames, rows := records[0], records[1:] 74 parsedColumnNames := make([]string, len(columnNames)) 75 for i, colName := range columnNames { 76 formattedColName := strings.TrimSpace(strings.ToLower(colName)) 77 parsedColName := strings.Replace(formattedColName, " ", "_", -1) 78 parsedColumnNames[i] = parsedColName 79 } 80 81 // create new generator for the tabular variable 82 description := "Variable " + name + " uploded through cli client." 83 newGenerator, err := api.CreateTabularVar(name, description, parsedColumnNames, singleUse) 84 if err != nil { 85 return err 86 } 87 88 // batch the rows and put them into a channel 89 numOfBatches := int(math.Ceil(float64(len(rows)) / float64(tabularBatchSize))) 90 rowsToUpload := make(chan [][]string, numOfBatches) 91 for i := 0; i < len(rows); i += tabularBatchSize { 92 batch := rows[i:min(i+tabularBatchSize, len(rows))] 93 rowsToUpload <- batch 94 } 95 close(rowsToUpload) 96 97 // chan to gather errors from workers 98 errors := make(chan error, numOfBatches) 99 100 log.Println("Beginning batch upload of csv file...") 101 102 // spawn workers to upload the rows 103 for i := 0; i < tabularConcurrency; i++ { 104 go rowUploadWorker(api, newGenerator, parsedColumnNames, rowsToUpload, errors) 105 } 106 107 for i := 0; i < numOfBatches; i++ { 108 if err := <-errors; err != nil { 109 return err 110 } 111 log.Printf("Tabular variable '%v' batch %v of %v uploaded.", name, i+1, numOfBatches) 112 } 113 114 return nil 115 } 116 117 // Well... yeah... 118 func min(a, b int) int { 119 if a <= b { 120 return a 121 } 122 return b 123 } 124 125 // rowUploadWorker is a helper worker which reads batch of rows to upload from rows chan 126 // and pushes potential errors through errorsChan 127 func rowUploadWorker(api tabularVariablesAPI, generator *rainforest.Generator, 128 columns []string, rowsChan <-chan [][]string, errorsChan chan<- error) { 129 for rows := range rowsChan { 130 error := api.AddGeneratorRowsFromTable(generator, columns, rows) 131 errorsChan <- error 132 } 133 } 134 135 // csvUpload is a wrapper around uploadTabularVar to function with csv-upload cli command 136 func csvUpload(c cliContext, api tabularVariablesAPI) error { 137 // Get the csv file path either from the option or command argument 138 filePath := c.Args().First() 139 if filePath == "" { 140 filePath = c.String("csv-file") 141 } 142 if filePath == "" { 143 return cli.NewExitError("CSV filename not specified", 1) 144 } 145 name := c.String("name") 146 if name == "" { 147 return cli.NewExitError("Tabular variable name not specified", 1) 148 } 149 overwrite := c.Bool("overwrite-variable") 150 singleUse := c.Bool("single-use") 151 152 err := uploadTabularVar(api, filePath, name, overwrite, singleUse) 153 if err != nil { 154 return cli.NewExitError(err.Error(), 1) 155 } 156 157 return nil 158 } 159 160 // preRunCSVUpload is a wrapper around uploadTabularVar to be ran before starting a new run 161 func preRunCSVUpload(c cliContext, api tabularVariablesAPI) error { 162 // Get the csv file path either and skip uploading if it's not present 163 filePath := c.String("import-variable-csv-file") 164 if filePath == "" { 165 return nil 166 } 167 name := c.String("import-variable-name") 168 if name == "" { 169 return errors.New("Tabular variable name not specified") 170 } 171 overwrite := c.Bool("overwrite-variable") 172 singleUse := c.Bool("single-use") 173 174 return uploadTabularVar(api, filePath, name, overwrite, singleUse) 175 }