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  }