...

Source file src/edge-infra.dev/pkg/lib/gcp/monitoring/monutil/monutil.go

Documentation: edge-infra.dev/pkg/lib/gcp/monitoring/monutil

     1  // Package monutil contains functions for file operations used with dashman and alertman
     2  package monutil
     3  
     4  import (
     5  	"bufio"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"log"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"regexp"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  
    18  	gptext "github.com/jedib0t/go-pretty/v6/text"
    19  )
    20  
    21  const (
    22  	foremanRegex = `ret-edge-(dev|stage|prod)\d-foreman(-\w+|)`
    23  )
    24  
    25  // TODO: these functions were copeied from dashboardmanager.go .  dashman should
    26  // be updated to use these functions
    27  
    28  // FileExists checks if file exists.
    29  func FileExists(filePath string) bool {
    30  	if _, err := os.Stat(filePath); err != nil && os.IsNotExist(err) {
    31  		return false
    32  	}
    33  	return true
    34  }
    35  
    36  // determines if a file represented by `path` is a directory or not
    37  func IsDirectory(path string) bool {
    38  	fileInfo, err := os.Stat(path)
    39  	if err != nil {
    40  		return false
    41  	}
    42  	return fileInfo.IsDir()
    43  }
    44  
    45  // FilterString returns a filtered string
    46  func FilterString(filter string, str string) string {
    47  	r := regexp.MustCompile(filter)
    48  	var submatches []string
    49  	matches := r.FindAllStringSubmatch(str, -1)
    50  	for _, v := range matches {
    51  		submatches = append(submatches, strings.Join(v, ""))
    52  	}
    53  	return strings.Join(submatches, "")
    54  }
    55  
    56  // ReconcileFileNames creates a new iterated duplicate file name.
    57  func ReconcileFileNames(names []string) []string {
    58  	var fileNames []string
    59  	for _, n := range names {
    60  		if !InStrList(n, fileNames) {
    61  			fileNames = append(fileNames, n)
    62  			continue
    63  		}
    64  
    65  		// find the next available iterator for the duplicate name
    66  		iter := getNextIter(fileNames, n+`_(Duplicate_Name_([\d]+)).json`)
    67  		fileNames = append(fileNames, n+"_(Duplicate_Name_"+strconv.Itoa(iter)+").json")
    68  	}
    69  
    70  	return fileNames
    71  }
    72  
    73  // returns a slice of strings from the second slice that are not present in the first slice
    74  func DiffStrSlice(s1 []string, s2 []string) []string {
    75  	var diff []string
    76  	if InSlice(s1, s2) { // slice values already in s1
    77  		return diff
    78  	}
    79  
    80  	for _, v := range s2 {
    81  		if !InStrList(v, s1) {
    82  			diff = append(diff, v)
    83  		}
    84  	}
    85  	return diff
    86  }
    87  
    88  // removes the second slice of strings from the first slice of strings, returns (s1 - s2)
    89  func RemoveStrSlice(s1 []string, s2 []string) []string {
    90  	remSlice := s1
    91  	for i := 0; i < len(s2); i++ {
    92  		remSlice = RemoveString(s2[i], remSlice)
    93  	}
    94  	return remSlice
    95  }
    96  
    97  // removes specified string from a slice of strings
    98  func RemoveString(str string, slice []string) []string {
    99  	if !InStrList(str, slice) {
   100  		return slice
   101  	}
   102  
   103  	var newSlice []string
   104  	for i := 0; i < len(slice); i++ {
   105  		if slice[i] != str {
   106  			newSlice = append(newSlice, slice[i])
   107  		}
   108  	}
   109  
   110  	return newSlice
   111  }
   112  
   113  // InList searches slice for string
   114  func InStrList(s string, l []string) bool {
   115  	for i := 0; i < len(l); i++ {
   116  		if l[i] == s {
   117  			return true
   118  		}
   119  	}
   120  	return false
   121  }
   122  
   123  // inIntList searches slice for int
   124  func InIntList(s int, l []int) bool {
   125  	for i := 0; i < len(l); i++ {
   126  		if l[i] == s {
   127  			return true
   128  		}
   129  	}
   130  	return false
   131  }
   132  
   133  // getNextIter returns the next available iterator based on a specified filter.
   134  // It's used to add iterators to a rename operation to prevent duplicates
   135  func getNextIter(list []string, filter string) int {
   136  	var iter []int
   137  	for _, l := range list {
   138  		r, _ := regexp.Compile(l + filter)
   139  		match := r.FindStringSubmatch(l)
   140  		if len(match) != 2 {
   141  			continue
   142  		}
   143  		mi, _ := strconv.Atoi(match[1])
   144  		iter = append(iter, mi)
   145  	}
   146  
   147  	// return next unused iterator
   148  	var newIter = 1
   149  	for i := 0; i < len(iter); i++ {
   150  		if InIntList(newIter, iter) {
   151  			newIter++
   152  		}
   153  	}
   154  
   155  	return newIter
   156  }
   157  
   158  // checks if the second slice is contained within the first slice
   159  func InSlice(s1 []string, s2 []string) bool {
   160  	// larger slice cannot be contained in a smaller slice
   161  	if len(s1) < len(s2) {
   162  		return false
   163  	}
   164  
   165  	// empty slice should not be considered a part of a slice with values
   166  	if len(s2) == 0 && !(len(s1) == 0) {
   167  		return false
   168  	}
   169  
   170  	// check that each value in slice 2 is present in slice 1
   171  	for _, v := range s2 {
   172  		if !InStrList(v, s1) {
   173  			return false
   174  		}
   175  	}
   176  	return true
   177  }
   178  
   179  // checks if the provided string contains a project ID that matches the foreman regex
   180  func IsForeman(p string) bool {
   181  	r := regexp.MustCompile(foremanRegex)
   182  	return r.MatchString(p)
   183  }
   184  
   185  // determines if the provided string is a list
   186  func IsList(s string) bool {
   187  	return len(strings.Split(s, ",")) > 1
   188  }
   189  
   190  // returns slice of strings that don't match between two slices
   191  func CompareStrSlice(a1 []string, a2 []string) []string {
   192  	return append(DiffStrSlice(a1, a2), DiffStrSlice(a2, a1)...)
   193  }
   194  
   195  // returns a mapped object of named regex
   196  func RegexNamedMatches(regex *regexp.Regexp, str string) map[string]string {
   197  	match := regex.FindStringSubmatch(str)
   198  
   199  	results := map[string]string{}
   200  	for i, name := range match {
   201  		results[regex.SubexpNames()[i]] = name
   202  	}
   203  	return results
   204  }
   205  
   206  // returns a mapped string for a given string slice
   207  func SliceToMap(s []string) map[string]string {
   208  	m := make(map[string]string)
   209  	for i := 0; i < len(s); i++ {
   210  		m[s[i]] = ""
   211  	}
   212  	return m
   213  }
   214  
   215  // returns a mapped string slice
   216  func MapSlice(strSlice []string) *map[string][]string {
   217  	strMap := make(map[string][]string)
   218  	for i := 0; i < len(strSlice); i++ {
   219  		strMap[strSlice[i]] = []string{}
   220  	}
   221  	return &strMap
   222  }
   223  
   224  // returns slice of keys of labelMap
   225  func Keys(m map[string]string) []string {
   226  	var keys []string
   227  	for k := range m {
   228  		keys = append(keys, k)
   229  	}
   230  	return keys
   231  }
   232  
   233  // Parse the rune as a bool.
   234  // Characters y or Y return true, n or N return false.
   235  // Everything else returns an error.
   236  func parsePrompt(s string) (bool, error) {
   237  	switch strings.ToLower(s) {
   238  	case "y":
   239  		return true, nil
   240  	case "n":
   241  		return false, nil
   242  	case "c":
   243  		return false, errors.New("cancel")
   244  	}
   245  	return false, errors.New("invalid keypress response")
   246  }
   247  
   248  // provides a yes/no/cancel prompt for the provided string
   249  func PromptYesNoCancel(s string) bool {
   250  	reader := bufio.NewReader(os.Stdin)
   251  	yes := White("[") + Magenta("Y") + White("es/")
   252  	no := Magenta("N") + White("o/")
   253  	cancel := Magenta("C") + White("ancel] : ")
   254  	fmt.Printf("%s %s%s%s", White(s), yes, no, cancel)
   255  
   256  	b := make([]byte, 1)
   257  	_, err := reader.Read(b)
   258  	if err != nil {
   259  		log.Fatal(err)
   260  	}
   261  
   262  	r, err := parsePrompt(string(b[0]))
   263  	if err != nil {
   264  		defer os.Exit(0)
   265  	}
   266  	return r
   267  }
   268  
   269  func runCmd(name string, arg ...string) {
   270  	cmd := exec.Command(name, arg...)
   271  	cmd.Stdout = os.Stdout
   272  	if err := cmd.Run(); err != nil {
   273  		fmt.Println(err.Error())
   274  	}
   275  }
   276  
   277  // returns a list of filepaths for a given file suffix found in a directory
   278  func ListFiles(rootPath string, fileSuffix string) ([]string, error) {
   279  	var files []string
   280  
   281  	if err := filepath.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error {
   282  		if err != nil {
   283  			return err
   284  		}
   285  
   286  		if !d.IsDir() && strings.HasSuffix(path, fileSuffix) {
   287  			files = append(files, path)
   288  		}
   289  		return nil
   290  	}); err != nil {
   291  		return nil, err
   292  	}
   293  	return files, nil
   294  }
   295  
   296  // clears the terminal screen
   297  func ClearTerminal() {
   298  	switch runtime.GOOS {
   299  	case "darwin":
   300  		runCmd("clear")
   301  	case "linux":
   302  		runCmd("clear")
   303  	case "windows":
   304  		runCmd("cmd", "/c", "cls")
   305  	default:
   306  		runCmd("clear")
   307  	}
   308  }
   309  
   310  func Bold(v interface{}) string {
   311  	return gptext.Bold.Sprint(v)
   312  }
   313  
   314  func Italic(v interface{}) string {
   315  	return gptext.Italic.Sprint(v)
   316  }
   317  
   318  func Underline(v interface{}) string {
   319  	return gptext.Underline.Sprint(v)
   320  }
   321  
   322  func Red(v interface{}) string {
   323  	return gptext.FgHiRed.Sprint(v)
   324  }
   325  
   326  func Blue(v interface{}) string {
   327  	return gptext.FgBlue.Sprint(v)
   328  }
   329  
   330  func Green(v interface{}) string {
   331  	return gptext.FgHiGreen.Sprint(v)
   332  }
   333  
   334  func Yellow(v interface{}) string {
   335  	return gptext.FgHiYellow.Sprint(v)
   336  }
   337  
   338  func Cyan(v interface{}) string {
   339  	return gptext.FgHiCyan.Sprint(v)
   340  }
   341  
   342  func White(v interface{}) string {
   343  	return gptext.FgHiWhite.Sprint(v)
   344  }
   345  
   346  func Magenta(v interface{}) string {
   347  	return gptext.FgHiMagenta.Sprint(v)
   348  }
   349  
   350  // Get the values from a map
   351  // func Values(items map[string]string) []string {
   352  // 	var values []string
   353  // 	for _, v := range items {
   354  // 		values = append(values, v)
   355  // 	}
   356  // 	return values
   357  // }
   358  
   359  // Splits a word string with format as key:value or only keys
   360  // to a slice. Then turns the slice into a map.
   361  // If string is only keys, the values in the map with be empty strings.
   362  func StringToMap(str string) map[string]string {
   363  	labelsMap := make(map[string]string)
   364  	splitString := regexp.MustCompile(`[\\,]+`).Split(str, -1)
   365  
   366  	for i := 0; i < len(splitString); i++ {
   367  		splitString[i] = strings.TrimSpace(splitString[i])
   368  	}
   369  
   370  	for i := 0; i < len(splitString); i++ {
   371  		if strings.Contains(splitString[i], ":") {
   372  			substr := regexp.MustCompile(`[\\:]+`).Split(splitString[i], -1)
   373  			labelsMap[substr[0]] = substr[1]
   374  			continue
   375  		}
   376  		labelsMap[splitString[i]] = ""
   377  	}
   378  	return labelsMap
   379  }
   380  
   381  // Check if a string array has duplicates
   382  func ContainsDuplicates(s []string) bool {
   383  	var l []string
   384  	for i := 0; i < len(s); i++ {
   385  		if InStrList(s[i], l) {
   386  			return true
   387  		}
   388  		l = append(l, s[i])
   389  	}
   390  	return false
   391  }
   392  

View as plain text