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  }