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