bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/cmd/synsec-cli/utils.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "os" 10 "strconv" 11 "strings" 12 "time" 13 14 "bitbucket.org/Aishee/synsec/pkg/cwhub" 15 "bitbucket.org/Aishee/synsec/pkg/cwversion" 16 "bitbucket.org/Aishee/synsec/pkg/types" 17 "github.com/enescakir/emoji" 18 "github.com/olekukonko/tablewriter" 19 dto "github.com/prometheus/client_model/go" 20 "github.com/prometheus/prom2json" 21 log "github.com/sirupsen/logrus" 22 "golang.org/x/mod/semver" 23 "gopkg.in/yaml.v2" 24 ) 25 26 func inSlice(s string, slice []string) bool { 27 for _, str := range slice { 28 if s == str { 29 return true 30 } 31 } 32 return false 33 } 34 35 func indexOf(s string, slice []string) int { 36 for i, elem := range slice { 37 if s == elem { 38 return i 39 } 40 } 41 return -1 42 } 43 44 func manageCliDecisionAlerts(ip *string, ipRange *string, scope *string, value *string) error { 45 46 /*if a range is provided, change the scope*/ 47 if *ipRange != "" { 48 _, _, err := net.ParseCIDR(*ipRange) 49 if err != nil { 50 return fmt.Errorf("%s isn't a valid range", *ipRange) 51 } 52 } 53 if *ip != "" { 54 ipRepr := net.ParseIP(*ip) 55 if ipRepr == nil { 56 return fmt.Errorf("%s isn't a valid ip", *ip) 57 } 58 } 59 60 //avoid confusion on scope (ip vs Ip and range vs Range) 61 switch strings.ToLower(*scope) { 62 case "ip": 63 *scope = types.Ip 64 case "range": 65 *scope = types.Range 66 } 67 return nil 68 } 69 70 func setHubBranch() error { 71 /* 72 if no branch has been specified in flags for the hub, then use the one corresponding to synsec version 73 */ 74 if cwhub.HubBranch == "" { 75 latest, err := cwversion.Latest() 76 if err != nil { 77 cwhub.HubBranch = "master" 78 return err 79 } 80 csVersion := cwversion.VersionStrip() 81 if csVersion == latest { 82 cwhub.HubBranch = "master" 83 } else if semver.Compare(csVersion, latest) == 1 { // if current version is greater than the latest we are in pre-release 84 log.Debugf("Your current synsec version seems to be a pre-release (%s)", csVersion) 85 cwhub.HubBranch = "master" 86 } else { 87 log.Warnf("Synsec is not the latest version. Current version is '%s' and the latest stable version is '%s'. Please update it!", csVersion, latest) 88 log.Warnf("As a result, you will not be able to use parsers/scenarios/collections added to Synsec Hub after SynSec %s", latest) 89 cwhub.HubBranch = csVersion 90 } 91 log.Debugf("Using branch '%s' for the hub", cwhub.HubBranch) 92 } 93 return nil 94 } 95 96 func ListItem(itemType string, args []string) { 97 98 var hubStatus []map[string]string 99 100 if len(args) == 1 { 101 hubStatus = cwhub.HubStatus(itemType, args[0], all) 102 } else { 103 hubStatus = cwhub.HubStatus(itemType, "", all) 104 } 105 106 if csConfig.Cscli.Output == "human" { 107 108 table := tablewriter.NewWriter(os.Stdout) 109 table.SetCenterSeparator("") 110 table.SetColumnSeparator("") 111 112 table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 113 table.SetAlignment(tablewriter.ALIGN_LEFT) 114 table.SetHeader([]string{"Name", fmt.Sprintf("%v Status", emoji.Package), "Version", "Local Path"}) 115 for _, v := range hubStatus { 116 table.Append([]string{v["name"], v["utf8_status"], v["local_version"], v["local_path"]}) 117 } 118 table.Render() 119 } else if csConfig.Cscli.Output == "json" { 120 x, err := json.MarshalIndent(hubStatus, "", " ") 121 if err != nil { 122 log.Fatalf("failed to unmarshal") 123 } 124 fmt.Printf("%s", string(x)) 125 } else if csConfig.Cscli.Output == "raw" { 126 for _, v := range hubStatus { 127 fmt.Printf("%s %s\n", v["name"], v["description"]) 128 } 129 } 130 } 131 132 func InstallItem(name string, obtype string, force bool) { 133 it := cwhub.GetItem(obtype, name) 134 if it == nil { 135 log.Fatalf("unable to retrive item : %s", name) 136 } 137 item := *it 138 if downloadOnly && item.Downloaded && item.UpToDate { 139 log.Warningf("%s is already downloaded and up-to-date", item.Name) 140 if !force { 141 return 142 } 143 } 144 item, err := cwhub.DownloadLatest(csConfig.Hub, item, force) 145 if err != nil { 146 log.Fatalf("error while downloading %s : %v", item.Name, err) 147 } 148 cwhub.AddItem(obtype, item) 149 if downloadOnly { 150 log.Infof("Downloaded %s to %s", item.Name, csConfig.Hub.HubDir+"/"+item.RemotePath) 151 return 152 } 153 item, err = cwhub.EnableItem(csConfig.Hub, item) 154 if err != nil { 155 log.Fatalf("error while enabled %s : %v.", item.Name, err) 156 } 157 cwhub.AddItem(obtype, item) 158 log.Infof("Enabled %s", item.Name) 159 return 160 } 161 162 func RemoveMany(itemType string, name string) { 163 var err error 164 var disabled int 165 if name != "" { 166 it := cwhub.GetItem(itemType, name) 167 if it == nil { 168 log.Fatalf("unable to retrieve: %s", name) 169 } 170 item := *it 171 item, err = cwhub.DisableItem(csConfig.Hub, item, purge, forceAction) 172 if err != nil { 173 log.Fatalf("unable to disable %s : %v", item.Name, err) 174 } 175 cwhub.AddItem(itemType, item) 176 return 177 } else if name == "" && all { 178 for _, v := range cwhub.GetItemMap(itemType) { 179 v, err = cwhub.DisableItem(csConfig.Hub, v, purge, forceAction) 180 if err != nil { 181 log.Fatalf("unable to disable %s : %v", v.Name, err) 182 } 183 cwhub.AddItem(itemType, v) 184 disabled++ 185 } 186 } 187 if name != "" && !all { 188 log.Errorf("%s not found", name) 189 return 190 } 191 log.Infof("Disabled %d items", disabled) 192 } 193 194 func UpgradeConfig(itemType string, name string, force bool) { 195 var err error 196 var updated int 197 var found bool 198 199 for _, v := range cwhub.GetItemMap(itemType) { 200 if name != "" && name != v.Name { 201 continue 202 } 203 if !v.Installed { 204 log.Tracef("skip %s, not installed", v.Name) 205 if !force { 206 continue 207 } 208 } 209 if !v.Downloaded { 210 log.Warningf("%s : not downloaded, please install.", v.Name) 211 if !force { 212 continue 213 } 214 } 215 found = true 216 if v.UpToDate { 217 log.Infof("%s : up-to-date", v.Name) 218 if !force { 219 continue 220 } 221 } 222 v, err = cwhub.DownloadLatest(csConfig.Hub, v, force) 223 if err != nil { 224 log.Fatalf("%s : download failed : %v", v.Name, err) 225 } 226 if !v.UpToDate { 227 if v.Tainted { 228 log.Infof("%v %s is tainted, --force to overwrite", emoji.Warning, v.Name) 229 } else if v.Local { 230 log.Infof("%v %s is local", emoji.Prohibited, v.Name) 231 } 232 } else { 233 log.Infof("%v %s : updated", emoji.Package, v.Name) 234 updated++ 235 } 236 cwhub.AddItem(itemType, v) 237 } 238 if !found && name == "" { 239 log.Infof("No %s installed, nothing to upgrade", itemType) 240 } else if !found { 241 log.Errorf("Item '%s' not found in hub", name) 242 } else if updated == 0 && found { 243 if name == "" { 244 log.Infof("All %s are already up-to-date", itemType) 245 } else { 246 log.Infof("Item '%s' is up-to-date", name) 247 } 248 } else if updated != 0 { 249 log.Infof("Upgraded %d items", updated) 250 } 251 252 } 253 254 func InspectItem(name string, objecitemType string) { 255 256 hubItem := cwhub.GetItem(objecitemType, name) 257 if hubItem == nil { 258 log.Fatalf("unable to retrieve item.") 259 } 260 buff, err := yaml.Marshal(*hubItem) 261 if err != nil { 262 log.Fatalf("unable to marshal item : %s", err) 263 } 264 fmt.Printf("%s", string(buff)) 265 if csConfig.Prometheus.Enabled { 266 if csConfig.Prometheus.ListenAddr == "" || csConfig.Prometheus.ListenPort == 0 { 267 log.Warningf("No prometheus address or port specified in '%s', can't show metrics", *csConfig.FilePath) 268 return 269 } 270 if prometheusURL == "" { 271 log.Debugf("No prometheus URL provided using: %s:%d", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort) 272 prometheusURL = fmt.Sprintf("http://%s:%d/metrics", csConfig.Prometheus.ListenAddr, csConfig.Prometheus.ListenPort) 273 } 274 fmt.Printf("\nCurrent metrics : \n\n") 275 ShowMetrics(hubItem) 276 } 277 } 278 279 func ShowMetrics(hubItem *cwhub.Item) { 280 switch hubItem.Type { 281 case cwhub.PARSERS: 282 metrics := GetParserMetric(prometheusURL, hubItem.Name) 283 ShowParserMetric(hubItem.Name, metrics) 284 case cwhub.SCENARIOS: 285 metrics := GetScenarioMetric(prometheusURL, hubItem.Name) 286 ShowScenarioMetric(hubItem.Name, metrics) 287 case cwhub.COLLECTIONS: 288 for _, item := range hubItem.Parsers { 289 metrics := GetParserMetric(prometheusURL, item) 290 ShowParserMetric(item, metrics) 291 } 292 for _, item := range hubItem.Scenarios { 293 metrics := GetScenarioMetric(prometheusURL, item) 294 ShowScenarioMetric(item, metrics) 295 } 296 for _, item := range hubItem.Collections { 297 hubItem := cwhub.GetItem(cwhub.COLLECTIONS, item) 298 if hubItem == nil { 299 log.Fatalf("unable to retrieve item '%s' from collection '%s'", item, hubItem.Name) 300 } 301 ShowMetrics(hubItem) 302 } 303 default: 304 log.Errorf("item of type '%s' is unknown", hubItem.Type) 305 } 306 } 307 308 /*This is a complete rip from prom2json*/ 309 func GetParserMetric(url string, itemName string) map[string]map[string]int { 310 stats := make(map[string]map[string]int) 311 312 result := GetPrometheusMetric(url) 313 for idx, fam := range result { 314 if !strings.HasPrefix(fam.Name, "cs_") { 315 continue 316 } 317 log.Tracef("round %d", idx) 318 for _, m := range fam.Metrics { 319 metric := m.(prom2json.Metric) 320 name, ok := metric.Labels["name"] 321 if !ok { 322 log.Debugf("no name in Metric %v", metric.Labels) 323 } 324 if name != itemName { 325 continue 326 } 327 source, ok := metric.Labels["source"] 328 if !ok { 329 log.Debugf("no source in Metric %v", metric.Labels) 330 } 331 value := m.(prom2json.Metric).Value 332 fval, err := strconv.ParseFloat(value, 32) 333 if err != nil { 334 log.Errorf("Unexpected int value %s : %s", value, err) 335 continue 336 } 337 ival := int(fval) 338 339 switch fam.Name { 340 case "cs_reader_hits_total": 341 if _, ok := stats[source]; !ok { 342 stats[source] = make(map[string]int) 343 stats[source]["parsed"] = 0 344 stats[source]["reads"] = 0 345 stats[source]["unparsed"] = 0 346 stats[source]["hits"] = 0 347 } 348 stats[source]["reads"] += ival 349 case "cs_parser_hits_ok_total": 350 if _, ok := stats[source]; !ok { 351 stats[source] = make(map[string]int) 352 } 353 stats[source]["parsed"] += ival 354 case "cs_parser_hits_ko_total": 355 if _, ok := stats[source]; !ok { 356 stats[source] = make(map[string]int) 357 } 358 stats[source]["unparsed"] += ival 359 case "cs_node_hits_total": 360 if _, ok := stats[source]; !ok { 361 stats[source] = make(map[string]int) 362 } 363 stats[source]["hits"] += ival 364 case "cs_node_hits_ok_total": 365 if _, ok := stats[source]; !ok { 366 stats[source] = make(map[string]int) 367 } 368 stats[source]["parsed"] += ival 369 case "cs_node_hits_ko_total": 370 if _, ok := stats[source]; !ok { 371 stats[source] = make(map[string]int) 372 } 373 stats[source]["unparsed"] += ival 374 default: 375 continue 376 } 377 } 378 } 379 return stats 380 } 381 382 func GetScenarioMetric(url string, itemName string) map[string]int { 383 stats := make(map[string]int) 384 385 stats["instanciation"] = 0 386 stats["curr_count"] = 0 387 stats["overflow"] = 0 388 stats["pour"] = 0 389 stats["underflow"] = 0 390 391 result := GetPrometheusMetric(url) 392 for idx, fam := range result { 393 if !strings.HasPrefix(fam.Name, "cs_") { 394 continue 395 } 396 log.Tracef("round %d", idx) 397 for _, m := range fam.Metrics { 398 metric := m.(prom2json.Metric) 399 name, ok := metric.Labels["name"] 400 if !ok { 401 log.Debugf("no name in Metric %v", metric.Labels) 402 } 403 if name != itemName { 404 continue 405 } 406 value := m.(prom2json.Metric).Value 407 fval, err := strconv.ParseFloat(value, 32) 408 if err != nil { 409 log.Errorf("Unexpected int value %s : %s", value, err) 410 continue 411 } 412 ival := int(fval) 413 414 switch fam.Name { 415 case "cs_bucket_created_total": 416 stats["instanciation"] += ival 417 case "cs_buckets": 418 stats["curr_count"] += ival 419 case "cs_bucket_overflowed_total": 420 stats["overflow"] += ival 421 case "cs_bucket_poured_total": 422 stats["pour"] += ival 423 case "cs_bucket_underflowed_total": 424 stats["underflow"] += ival 425 default: 426 continue 427 } 428 } 429 } 430 return stats 431 } 432 433 func GetPrometheusMetric(url string) []*prom2json.Family { 434 mfChan := make(chan *dto.MetricFamily, 1024) 435 436 // Start with the DefaultTransport for sane defaults. 437 transport := http.DefaultTransport.(*http.Transport).Clone() 438 // Conservatively disable HTTP keep-alives as this program will only 439 // ever need a single HTTP request. 440 transport.DisableKeepAlives = true 441 // Timeout early if the server doesn't even return the headers. 442 transport.ResponseHeaderTimeout = time.Minute 443 444 go func() { 445 defer types.CatchPanic("synsec/GetPrometheusMetric") 446 err := prom2json.FetchMetricFamilies(url, mfChan, transport) 447 if err != nil { 448 log.Fatalf("failed to fetch prometheus metrics : %v", err) 449 } 450 }() 451 452 result := []*prom2json.Family{} 453 for mf := range mfChan { 454 result = append(result, prom2json.NewFamily(mf)) 455 } 456 log.Debugf("Finished reading prometheus output, %d entries", len(result)) 457 458 return result 459 } 460 461 func ShowScenarioMetric(itemName string, metrics map[string]int) { 462 if metrics["instanciation"] == 0 { 463 return 464 } 465 table := tablewriter.NewWriter(os.Stdout) 466 table.SetHeader([]string{"Current Count", "Overflows", "Instanciated", "Poured", "Expired"}) 467 table.Append([]string{fmt.Sprintf("%d", metrics["curr_count"]), fmt.Sprintf("%d", metrics["overflow"]), fmt.Sprintf("%d", metrics["instanciation"]), fmt.Sprintf("%d", metrics["pour"]), fmt.Sprintf("%d", metrics["underflow"])}) 468 469 fmt.Printf(" - (Scenario) %s: \n", itemName) 470 table.Render() 471 fmt.Println() 472 } 473 474 func ShowParserMetric(itemName string, metrics map[string]map[string]int) { 475 skip := true 476 477 table := tablewriter.NewWriter(os.Stdout) 478 table.SetHeader([]string{"Parsers", "Hits", "Parsed", "Unparsed"}) 479 for source, stats := range metrics { 480 if stats["hits"] > 0 { 481 table.Append([]string{source, fmt.Sprintf("%d", stats["hits"]), fmt.Sprintf("%d", stats["parsed"]), fmt.Sprintf("%d", stats["unparsed"])}) 482 skip = false 483 } 484 } 485 if !skip { 486 fmt.Printf(" - (Parser) %s: \n", itemName) 487 table.Render() 488 fmt.Println() 489 } 490 } 491 492 //it's a rip of the cli version, but in silent-mode 493 func silenceInstallItem(name string, obtype string) (string, error) { 494 var item *cwhub.Item 495 item = cwhub.GetItem(obtype, name) 496 if item == nil { 497 return "", fmt.Errorf("error retrieving item") 498 } 499 it := *item 500 if downloadOnly && it.Downloaded && it.UpToDate { 501 return fmt.Sprintf("%s is already downloaded and up-to-date", it.Name), nil 502 } 503 it, err := cwhub.DownloadLatest(csConfig.Hub, it, forceAction) 504 if err != nil { 505 return "", fmt.Errorf("error while downloading %s : %v", it.Name, err) 506 } 507 if err := cwhub.AddItem(obtype, it); err != nil { 508 return "", err 509 } 510 511 if downloadOnly { 512 return fmt.Sprintf("Downloaded %s to %s", it.Name, csConfig.Cscli.HubDir+"/"+it.RemotePath), nil 513 } 514 it, err = cwhub.EnableItem(csConfig.Hub, it) 515 if err != nil { 516 return "", fmt.Errorf("error while enabled %s : %v", it.Name, err) 517 } 518 if err := cwhub.AddItem(obtype, it); err != nil { 519 return "", err 520 } 521 return fmt.Sprintf("Enabled %s", it.Name), nil 522 } 523 524 func RestoreHub(dirPath string) error { 525 var err error 526 527 for _, itype := range cwhub.ItemTypes { 528 itemDirectory := fmt.Sprintf("%s/%s/", dirPath, itype) 529 if _, err = os.Stat(itemDirectory); err != nil { 530 log.Infof("no %s in backup", itype) 531 continue 532 } 533 /*restore the upstream items*/ 534 upstreamListFN := fmt.Sprintf("%s/upstream-%s.json", itemDirectory, itype) 535 file, err := ioutil.ReadFile(upstreamListFN) 536 if err != nil { 537 return fmt.Errorf("error while opening %s : %s", upstreamListFN, err) 538 } 539 var upstreamList []string 540 err = json.Unmarshal([]byte(file), &upstreamList) 541 if err != nil { 542 return fmt.Errorf("error unmarshaling %s : %s", upstreamListFN, err) 543 } 544 for _, toinstall := range upstreamList { 545 label, err := silenceInstallItem(toinstall, itype) 546 if err != nil { 547 log.Errorf("Error while installing %s : %s", toinstall, err) 548 } else if label != "" { 549 log.Infof("Installed %s : %s", toinstall, label) 550 } else { 551 log.Printf("Installed %s : ok", toinstall) 552 } 553 } 554 555 /*restore the local and tainted items*/ 556 files, err := ioutil.ReadDir(itemDirectory) 557 if err != nil { 558 return fmt.Errorf("failed enumerating files of %s : %s", itemDirectory, err) 559 } 560 for _, file := range files { 561 //this was the upstream data 562 if file.Name() == fmt.Sprintf("upstream-%s.json", itype) { 563 continue 564 } 565 if itype == cwhub.PARSERS || itype == cwhub.PARSERS_OVFLW { 566 //we expect a stage here 567 if !file.IsDir() { 568 continue 569 } 570 stage := file.Name() 571 stagedir := fmt.Sprintf("%s/%s/%s/", csConfig.ConfigPaths.ConfigDir, itype, stage) 572 log.Debugf("Found stage %s in %s, target directory : %s", stage, itype, stagedir) 573 if err = os.MkdirAll(stagedir, os.ModePerm); err != nil { 574 return fmt.Errorf("error while creating stage directory %s : %s", stagedir, err) 575 } 576 /*find items*/ 577 ifiles, err := ioutil.ReadDir(itemDirectory + "/" + stage + "/") 578 if err != nil { 579 return fmt.Errorf("failed enumerating files of %s : %s", itemDirectory+"/"+stage, err) 580 } 581 //finaly copy item 582 for _, tfile := range ifiles { 583 log.Infof("Going to restore local/tainted [%s]", tfile.Name()) 584 sourceFile := fmt.Sprintf("%s/%s/%s", itemDirectory, stage, tfile.Name()) 585 destinationFile := fmt.Sprintf("%s%s", stagedir, tfile.Name()) 586 if err = types.CopyFile(sourceFile, destinationFile); err != nil { 587 return fmt.Errorf("failed copy %s %s to %s : %s", itype, sourceFile, destinationFile, err) 588 } 589 log.Infof("restored %s to %s", sourceFile, destinationFile) 590 } 591 } else { 592 log.Infof("Going to restore local/tainted [%s]", file.Name()) 593 sourceFile := fmt.Sprintf("%s/%s", itemDirectory, file.Name()) 594 destinationFile := fmt.Sprintf("%s/%s/%s", csConfig.ConfigPaths.ConfigDir, itype, file.Name()) 595 if err = types.CopyFile(sourceFile, destinationFile); err != nil { 596 return fmt.Errorf("failed copy %s %s to %s : %s", itype, sourceFile, destinationFile, err) 597 } 598 log.Infof("restored %s to %s", sourceFile, destinationFile) 599 } 600 601 } 602 } 603 return nil 604 } 605 606 func BackupHub(dirPath string) error { 607 var err error 608 var itemDirectory string 609 var upstreamParsers []string 610 611 for _, itemType := range cwhub.ItemTypes { 612 clog := log.WithFields(log.Fields{ 613 "type": itemType, 614 }) 615 itemMap := cwhub.GetItemMap(itemType) 616 if itemMap != nil { 617 itemDirectory = fmt.Sprintf("%s/%s/", dirPath, itemType) 618 if err := os.MkdirAll(itemDirectory, os.ModePerm); err != nil { 619 return fmt.Errorf("error while creating %s : %s", itemDirectory, err) 620 } 621 upstreamParsers = []string{} 622 for k, v := range itemMap { 623 clog = clog.WithFields(log.Fields{ 624 "file": v.Name, 625 }) 626 if !v.Installed { //only backup installed ones 627 clog.Debugf("[%s] : not installed", k) 628 continue 629 } 630 631 //for the local/tainted ones, we backup the full file 632 if v.Tainted || v.Local || !v.UpToDate { 633 //we need to backup stages for parsers 634 if itemType == cwhub.PARSERS || itemType == cwhub.PARSERS_OVFLW { 635 fstagedir := fmt.Sprintf("%s%s", itemDirectory, v.Stage) 636 if err := os.MkdirAll(fstagedir, os.ModePerm); err != nil { 637 return fmt.Errorf("error while creating stage dir %s : %s", fstagedir, err) 638 } 639 } 640 clog.Debugf("[%s] : backuping file (tainted:%t local:%t up-to-date:%t)", k, v.Tainted, v.Local, v.UpToDate) 641 tfile := fmt.Sprintf("%s%s/%s", itemDirectory, v.Stage, v.FileName) 642 if err = types.CopyFile(v.LocalPath, tfile); err != nil { 643 return fmt.Errorf("failed copy %s %s to %s : %s", itemType, v.LocalPath, tfile, err) 644 } 645 clog.Infof("local/tainted saved %s to %s", v.LocalPath, tfile) 646 continue 647 } 648 clog.Debugf("[%s] : from hub, just backup name (up-to-date:%t)", k, v.UpToDate) 649 clog.Infof("saving, version:%s, up-to-date:%t", v.Version, v.UpToDate) 650 upstreamParsers = append(upstreamParsers, v.Name) 651 } 652 //write the upstream items 653 upstreamParsersFname := fmt.Sprintf("%s/upstream-%s.json", itemDirectory, itemType) 654 upstreamParsersContent, err := json.MarshalIndent(upstreamParsers, "", " ") 655 if err != nil { 656 return fmt.Errorf("failed marshaling upstream parsers : %s", err) 657 } 658 err = ioutil.WriteFile(upstreamParsersFname, upstreamParsersContent, 0644) 659 if err != nil { 660 return fmt.Errorf("unable to write to %s %s : %s", itemType, upstreamParsersFname, err) 661 } 662 clog.Infof("Wrote %d entries for %s to %s", len(upstreamParsers), itemType, upstreamParsersFname) 663 664 } else { 665 clog.Infof("No %s to backup.", itemType) 666 } 667 } 668 669 return nil 670 }