github.com/banmanh482/nomad@v0.11.8/helper/funcs.go (about) 1 package helper 2 3 import ( 4 "crypto/sha512" 5 "fmt" 6 "path/filepath" 7 "reflect" 8 "regexp" 9 "strings" 10 "time" 11 12 multierror "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/hcl/hcl/ast" 14 ) 15 16 // validUUID is used to check if a given string looks like a UUID 17 var validUUID = regexp.MustCompile(`(?i)^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`) 18 19 // validInterpVarKey matches valid dotted variable names for interpolation. The 20 // string must begin with one or more non-dot characters which may be followed 21 // by sequences containing a dot followed by a one or more non-dot characters. 22 var validInterpVarKey = regexp.MustCompile(`^[^.]+(\.[^.]+)*$`) 23 24 // IsUUID returns true if the given string is a valid UUID. 25 func IsUUID(str string) bool { 26 const uuidLen = 36 27 if len(str) != uuidLen { 28 return false 29 } 30 31 return validUUID.MatchString(str) 32 } 33 34 // IsValidInterpVariable returns true if a valid dotted variable names for 35 // interpolation. The string must begin with one or more non-dot characters 36 // which may be followed by sequences containing a dot followed by a one or more 37 // non-dot characters. 38 func IsValidInterpVariable(str string) bool { 39 return validInterpVarKey.MatchString(str) 40 } 41 42 // HashUUID takes an input UUID and returns a hashed version of the UUID to 43 // ensure it is well distributed. 44 func HashUUID(input string) (output string, hashed bool) { 45 if !IsUUID(input) { 46 return "", false 47 } 48 49 // Hash the input 50 buf := sha512.Sum512([]byte(input)) 51 output = fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", 52 buf[0:4], 53 buf[4:6], 54 buf[6:8], 55 buf[8:10], 56 buf[10:16]) 57 58 return output, true 59 } 60 61 // boolToPtr returns the pointer to a boolean 62 func BoolToPtr(b bool) *bool { 63 return &b 64 } 65 66 // IntToPtr returns the pointer to an int 67 func IntToPtr(i int) *int { 68 return &i 69 } 70 71 // Int8ToPtr returns the pointer to an int8 72 func Int8ToPtr(i int8) *int8 { 73 return &i 74 } 75 76 // Int64ToPtr returns the pointer to an int 77 func Int64ToPtr(i int64) *int64 { 78 return &i 79 } 80 81 // Uint64ToPtr returns the pointer to an uint64 82 func Uint64ToPtr(u uint64) *uint64 { 83 return &u 84 } 85 86 // UintToPtr returns the pointer to an uint 87 func UintToPtr(u uint) *uint { 88 return &u 89 } 90 91 // StringToPtr returns the pointer to a string 92 func StringToPtr(str string) *string { 93 return &str 94 } 95 96 // TimeToPtr returns the pointer to a time stamp 97 func TimeToPtr(t time.Duration) *time.Duration { 98 return &t 99 } 100 101 // Float64ToPtr returns the pointer to an float64 102 func Float64ToPtr(f float64) *float64 { 103 return &f 104 } 105 106 func IntMin(a, b int) int { 107 if a < b { 108 return a 109 } 110 return b 111 } 112 113 func IntMax(a, b int) int { 114 if a > b { 115 return a 116 } 117 return b 118 } 119 120 func Uint64Max(a, b uint64) uint64 { 121 if a > b { 122 return a 123 } 124 return b 125 } 126 127 // MapStringStringSliceValueSet returns the set of values in a map[string][]string 128 func MapStringStringSliceValueSet(m map[string][]string) []string { 129 set := make(map[string]struct{}) 130 for _, slice := range m { 131 for _, v := range slice { 132 set[v] = struct{}{} 133 } 134 } 135 136 flat := make([]string, 0, len(set)) 137 for k := range set { 138 flat = append(flat, k) 139 } 140 return flat 141 } 142 143 func SliceStringToSet(s []string) map[string]struct{} { 144 m := make(map[string]struct{}, (len(s)+1)/2) 145 for _, k := range s { 146 m[k] = struct{}{} 147 } 148 return m 149 } 150 151 // SliceStringIsSubset returns whether the smaller set of strings is a subset of 152 // the larger. If the smaller slice is not a subset, the offending elements are 153 // returned. 154 func SliceStringIsSubset(larger, smaller []string) (bool, []string) { 155 largerSet := make(map[string]struct{}, len(larger)) 156 for _, l := range larger { 157 largerSet[l] = struct{}{} 158 } 159 160 subset := true 161 var offending []string 162 for _, s := range smaller { 163 if _, ok := largerSet[s]; !ok { 164 subset = false 165 offending = append(offending, s) 166 } 167 } 168 169 return subset, offending 170 } 171 172 func SliceSetDisjoint(first, second []string) (bool, []string) { 173 contained := make(map[string]struct{}, len(first)) 174 for _, k := range first { 175 contained[k] = struct{}{} 176 } 177 178 offending := make(map[string]struct{}) 179 for _, k := range second { 180 if _, ok := contained[k]; ok { 181 offending[k] = struct{}{} 182 } 183 } 184 185 if len(offending) == 0 { 186 return true, nil 187 } 188 189 flattened := make([]string, 0, len(offending)) 190 for k := range offending { 191 flattened = append(flattened, k) 192 } 193 return false, flattened 194 } 195 196 // CompareSliceSetString returns true if the slices contain the same strings. 197 // Order is ignored. The slice may be copied but is never altered. The slice is 198 // assumed to be a set. Multiple instances of an entry are treated the same as 199 // a single instance. 200 func CompareSliceSetString(a, b []string) bool { 201 n := len(a) 202 if n != len(b) { 203 return false 204 } 205 206 // Copy a into a map and compare b against it 207 amap := make(map[string]struct{}, n) 208 for i := range a { 209 amap[a[i]] = struct{}{} 210 } 211 212 for i := range b { 213 if _, ok := amap[b[i]]; !ok { 214 return false 215 } 216 } 217 218 return true 219 } 220 221 // CompareMapStringString returns true if the maps are equivalent. A nil and 222 // empty map are considered not equal. 223 func CompareMapStringString(a, b map[string]string) bool { 224 if a == nil || b == nil { 225 return a == nil && b == nil 226 } 227 228 if len(a) != len(b) { 229 return false 230 } 231 232 for k, v := range a { 233 v2, ok := b[k] 234 if !ok { 235 return false 236 } 237 if v != v2 { 238 return false 239 } 240 } 241 242 // Already compared all known values in a so only test that keys from b 243 // exist in a 244 for k := range b { 245 if _, ok := a[k]; !ok { 246 return false 247 } 248 } 249 250 return true 251 } 252 253 // Helpers for copying generic structures. 254 func CopyMapStringString(m map[string]string) map[string]string { 255 l := len(m) 256 if l == 0 { 257 return nil 258 } 259 260 c := make(map[string]string, l) 261 for k, v := range m { 262 c[k] = v 263 } 264 return c 265 } 266 267 func CopyMapStringStruct(m map[string]struct{}) map[string]struct{} { 268 l := len(m) 269 if l == 0 { 270 return nil 271 } 272 273 c := make(map[string]struct{}, l) 274 for k := range m { 275 c[k] = struct{}{} 276 } 277 return c 278 } 279 280 func CopyMapStringInt(m map[string]int) map[string]int { 281 l := len(m) 282 if l == 0 { 283 return nil 284 } 285 286 c := make(map[string]int, l) 287 for k, v := range m { 288 c[k] = v 289 } 290 return c 291 } 292 293 func CopyMapStringFloat64(m map[string]float64) map[string]float64 { 294 l := len(m) 295 if l == 0 { 296 return nil 297 } 298 299 c := make(map[string]float64, l) 300 for k, v := range m { 301 c[k] = v 302 } 303 return c 304 } 305 306 // CopyMapStringSliceString copies a map of strings to string slices such as 307 // http.Header 308 func CopyMapStringSliceString(m map[string][]string) map[string][]string { 309 l := len(m) 310 if l == 0 { 311 return nil 312 } 313 314 c := make(map[string][]string, l) 315 for k, v := range m { 316 c[k] = CopySliceString(v) 317 } 318 return c 319 } 320 321 func CopySliceString(s []string) []string { 322 l := len(s) 323 if l == 0 { 324 return nil 325 } 326 327 c := make([]string, l) 328 for i, v := range s { 329 c[i] = v 330 } 331 return c 332 } 333 334 func CopySliceInt(s []int) []int { 335 l := len(s) 336 if l == 0 { 337 return nil 338 } 339 340 c := make([]int, l) 341 for i, v := range s { 342 c[i] = v 343 } 344 return c 345 } 346 347 // CleanEnvVar replaces all occurrences of illegal characters in an environment 348 // variable with the specified byte. 349 func CleanEnvVar(s string, r byte) string { 350 b := []byte(s) 351 for i, c := range b { 352 switch { 353 case c == '_': 354 case c == '.': 355 case c >= 'a' && c <= 'z': 356 case c >= 'A' && c <= 'Z': 357 case i > 0 && c >= '0' && c <= '9': 358 default: 359 // Replace! 360 b[i] = r 361 } 362 } 363 return string(b) 364 } 365 366 func CheckHCLKeys(node ast.Node, valid []string) error { 367 var list *ast.ObjectList 368 switch n := node.(type) { 369 case *ast.ObjectList: 370 list = n 371 case *ast.ObjectType: 372 list = n.List 373 default: 374 return fmt.Errorf("cannot check HCL keys of type %T", n) 375 } 376 377 validMap := make(map[string]struct{}, len(valid)) 378 for _, v := range valid { 379 validMap[v] = struct{}{} 380 } 381 382 var result error 383 for _, item := range list.Items { 384 key := item.Keys[0].Token.Value().(string) 385 if _, ok := validMap[key]; !ok { 386 result = multierror.Append(result, fmt.Errorf( 387 "invalid key: %s", key)) 388 } 389 } 390 391 return result 392 } 393 394 // UnusedKeys returns a pretty-printed error if any `hcl:",unusedKeys"` is not empty 395 func UnusedKeys(obj interface{}) error { 396 val := reflect.ValueOf(obj) 397 if val.Kind() == reflect.Ptr { 398 val = reflect.Indirect(val) 399 } 400 return unusedKeysImpl([]string{}, val) 401 } 402 403 func unusedKeysImpl(path []string, val reflect.Value) error { 404 stype := val.Type() 405 for i := 0; i < stype.NumField(); i++ { 406 ftype := stype.Field(i) 407 fval := val.Field(i) 408 tags := strings.Split(ftype.Tag.Get("hcl"), ",") 409 name := tags[0] 410 tags = tags[1:] 411 412 if fval.Kind() == reflect.Ptr { 413 fval = reflect.Indirect(fval) 414 } 415 416 // struct? recurse. Add the struct's key to the path 417 if fval.Kind() == reflect.Struct { 418 err := unusedKeysImpl(append([]string{name}, path...), fval) 419 if err != nil { 420 return err 421 } 422 continue 423 } 424 425 // Search the hcl tags for "unusedKeys" 426 unusedKeys := false 427 for _, p := range tags { 428 if p == "unusedKeys" { 429 unusedKeys = true 430 break 431 } 432 } 433 434 if unusedKeys { 435 ks, ok := fval.Interface().([]string) 436 if ok && len(ks) != 0 { 437 ps := "" 438 if len(path) > 0 { 439 ps = strings.Join(path, ".") + " " 440 } 441 return fmt.Errorf("%sunexpected keys %s", 442 ps, 443 strings.Join(ks, ", ")) 444 } 445 } 446 } 447 return nil 448 } 449 450 // RemoveEqualFold removes the first string that EqualFold matches. It updates xs in place 451 func RemoveEqualFold(xs *[]string, search string) { 452 sl := *xs 453 for i, x := range sl { 454 if strings.EqualFold(x, search) { 455 sl = append(sl[:i], sl[i+1:]...) 456 if len(sl) == 0 { 457 *xs = nil 458 } else { 459 *xs = sl 460 } 461 return 462 } 463 } 464 } 465 466 // PathEscapesSandbox returns whether previously cleaned path inside the 467 // sandbox directory (typically this will be the allocation directory) 468 // escapes. 469 func PathEscapesSandbox(sandboxDir, path string) bool { 470 rel, err := filepath.Rel(sandboxDir, path) 471 if err != nil { 472 return true 473 } 474 if strings.HasPrefix(rel, "..") { 475 return true 476 } 477 return false 478 }