package helper import ( "crypto/sha512" "fmt" "regexp" "strconv" "strings" "time" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl/hcl/ast" ) // validUUID is used to check if a given string looks like a UUID var validUUID = regexp.MustCompile(`(?i)^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`) // validInterpVarKey matches valid dotted variable names for interpolation. The // string must begin with one or more non-dot characters which may be followed // by sequences containing a dot followed by a one or more non-dot characters. var validInterpVarKey = regexp.MustCompile(`^[^.]+(\.[^.]+)*$`) // IsUUID returns true if the given string is a valid UUID. func IsUUID(str string) bool { const uuidLen = 36 if len(str) != uuidLen { return false } return validUUID.MatchString(str) } // IsValidInterpVariable returns true if a valid dotted variable names for // interpolation. The string must begin with one or more non-dot characters // which may be followed by sequences containing a dot followed by a one or more // non-dot characters. func IsValidInterpVariable(str string) bool { return validInterpVarKey.MatchString(str) } // HashUUID takes an input UUID and returns a hashed version of the UUID to // ensure it is well distributed. func HashUUID(input string) (output string, hashed bool) { if !IsUUID(input) { return "", false } // Hash the input buf := sha512.Sum512([]byte(input)) output = fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", buf[0:4], buf[4:6], buf[6:8], buf[8:10], buf[10:16]) return output, true } // boolToPtr returns the pointer to a boolean func BoolToPtr(b bool) *bool { return &b } // IntToPtr returns the pointer to an int func IntToPtr(i int) *int { return &i } // Int64ToPtr returns the pointer to an int func Int64ToPtr(i int64) *int64 { return &i } // Uint64ToPtr returns the pointer to an uint64 func Uint64ToPtr(u uint64) *uint64 { return &u } // UintToPtr returns the pointer to an uint func UintToPtr(u uint) *uint { return &u } // StringToPtr returns the pointer to a string func StringToPtr(str string) *string { return &str } // TimeToPtr returns the pointer to a time stamp func TimeToPtr(t time.Duration) *time.Duration { return &t } // Float64ToPtr returns the pointer to an float64 func Float64ToPtr(f float64) *float64 { return &f } func IntMin(a, b int) int { if a < b { return a } return b } func IntMax(a, b int) int { if a > b { return a } return b } func Uint64Max(a, b uint64) uint64 { if a > b { return a } return b } // MapStringStringSliceValueSet returns the set of values in a map[string][]string func MapStringStringSliceValueSet(m map[string][]string) []string { set := make(map[string]struct{}) for _, slice := range m { for _, v := range slice { set[v] = struct{}{} } } flat := make([]string, 0, len(set)) for k := range set { flat = append(flat, k) } return flat } func SliceStringToSet(s []string) map[string]struct{} { m := make(map[string]struct{}, (len(s)+1)/2) for _, k := range s { m[k] = struct{}{} } return m } // SliceStringIsSubset returns whether the smaller set of strings is a subset of // the larger. If the smaller slice is not a subset, the offending elements are // returned. func SliceStringIsSubset(larger, smaller []string) (bool, []string) { largerSet := make(map[string]struct{}, len(larger)) for _, l := range larger { largerSet[l] = struct{}{} } subset := true var offending []string for _, s := range smaller { if _, ok := largerSet[s]; !ok { subset = false offending = append(offending, s) } } return subset, offending } func SliceSetDisjoint(first, second []string) (bool, []string) { contained := make(map[string]struct{}, len(first)) for _, k := range first { contained[k] = struct{}{} } offending := make(map[string]struct{}) for _, k := range second { if _, ok := contained[k]; ok { offending[k] = struct{}{} } } if len(offending) == 0 { return true, nil } flattened := make([]string, 0, len(offending)) for k := range offending { flattened = append(flattened, k) } return false, flattened } // CompareMapStringString returns true if the maps are equivalent. A nil and // empty map are considered not equal. func CompareMapStringString(a, b map[string]string) bool { if a == nil || b == nil { return a == nil && b == nil } if len(a) != len(b) { return false } for k, v := range a { v2, ok := b[k] if !ok { return false } if v != v2 { return false } } // Already compared all known values in a so only test that keys from b // exist in a for k := range b { if _, ok := a[k]; !ok { return false } } return true } // Helpers for copying generic structures. func CopyMapStringString(m map[string]string) map[string]string { l := len(m) if l == 0 { return nil } c := make(map[string]string, l) for k, v := range m { c[k] = v } return c } func CopyMapStringStruct(m map[string]struct{}) map[string]struct{} { l := len(m) if l == 0 { return nil } c := make(map[string]struct{}, l) for k := range m { c[k] = struct{}{} } return c } func CopyMapStringInt(m map[string]int) map[string]int { l := len(m) if l == 0 { return nil } c := make(map[string]int, l) for k, v := range m { c[k] = v } return c } func CopyMapStringFloat64(m map[string]float64) map[string]float64 { l := len(m) if l == 0 { return nil } c := make(map[string]float64, l) for k, v := range m { c[k] = v } return c } // CopyMapStringSliceString copies a map of strings to string slices such as // http.Header func CopyMapStringSliceString(m map[string][]string) map[string][]string { l := len(m) if l == 0 { return nil } c := make(map[string][]string, l) for k, v := range m { c[k] = CopySliceString(v) } return c } func CopySliceString(s []string) []string { l := len(s) if l == 0 { return nil } c := make([]string, l) for i, v := range s { c[i] = v } return c } func CopySliceInt(s []int) []int { l := len(s) if l == 0 { return nil } c := make([]int, l) for i, v := range s { c[i] = v } return c } // CleanEnvVar replaces all occurrences of illegal characters in an environment // variable with the specified byte. func CleanEnvVar(s string, r byte) string { b := []byte(s) for i, c := range b { switch { case c == '_': case c == '.': case c >= 'a' && c <= 'z': case c >= 'A' && c <= 'Z': case i > 0 && c >= '0' && c <= '9': default: // Replace! b[i] = r } } return string(b) } func CheckHCLKeys(node ast.Node, valid []string) error { var list *ast.ObjectList switch n := node.(type) { case *ast.ObjectList: list = n case *ast.ObjectType: list = n.List default: return fmt.Errorf("cannot check HCL keys of type %T", n) } validMap := make(map[string]struct{}, len(valid)) for _, v := range valid { validMap[v] = struct{}{} } var result error for _, item := range list.Items { key := item.Keys[0].Token.Value().(string) if _, ok := validMap[key]; !ok { result = multierror.Append(result, fmt.Errorf( "invalid key: %s", key)) } } return result } // FormatFloat converts the floating-point number f to a string, // after rounding it to the passed unit. // // Uses 'f' format (-ddd.dddddd, no exponent), and uses at most // maxPrec digits after the decimal point. func FormatFloat(f float64, maxPrec int) string { v := strconv.FormatFloat(f, 'f', -1, 64) idx := strings.LastIndex(v, ".") if idx == -1 { return v } sublen := idx + maxPrec + 1 if sublen > len(v) { sublen = len(v) } return v[:sublen] }