github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/configs/parsing_helpers.go (about)

     1  package configs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/runtime"
    12  )
    13  
    14  // There seems to be no composite interface in the kubernetes api package,
    15  // so we have to declare our own.
    16  type apiObject interface {
    17  	v1.Object
    18  	runtime.Object
    19  }
    20  
    21  // GetMapKeyAsBool searches the map for the given key and parses the key as bool.
    22  func GetMapKeyAsBool(m map[string]string, key string, context apiObject) (bool, bool, error) {
    23  	if str, exists := m[key]; exists {
    24  		b, err := ParseBool(str)
    25  		if err != nil {
    26  			return false, exists, fmt.Errorf("%s %v/%v '%s' contains invalid bool: %w, ignoring", context.GetObjectKind().GroupVersionKind().Kind, context.GetNamespace(), context.GetName(), key, err)
    27  		}
    28  
    29  		return b, exists, nil
    30  	}
    31  
    32  	return false, false, nil
    33  }
    34  
    35  // GetMapKeyAsInt tries to find and parse a key in a map as int.
    36  func GetMapKeyAsInt(m map[string]string, key string, context apiObject) (int, bool, error) {
    37  	if str, exists := m[key]; exists {
    38  		i, err := ParseInt(str)
    39  		if err != nil {
    40  			return 0, exists, fmt.Errorf("%s %v/%v '%s' contains invalid integer: %w, ignoring", context.GetObjectKind().GroupVersionKind().Kind, context.GetNamespace(), context.GetName(), key, err)
    41  		}
    42  
    43  		return i, exists, nil
    44  	}
    45  
    46  	return 0, false, nil
    47  }
    48  
    49  // GetMapKeyAsInt64 tries to find and parse a key in a map as int64.
    50  func GetMapKeyAsInt64(m map[string]string, key string, context apiObject) (int64, bool, error) {
    51  	if str, exists := m[key]; exists {
    52  		i, err := ParseInt64(str)
    53  		if err != nil {
    54  			return 0, exists, fmt.Errorf("%s %v/%v '%s' contains invalid integer: %w, ignoring", context.GetObjectKind().GroupVersionKind().Kind, context.GetNamespace(), context.GetName(), key, err)
    55  		}
    56  
    57  		return i, exists, nil
    58  	}
    59  
    60  	return 0, false, nil
    61  }
    62  
    63  // GetMapKeyAsUint64 tries to find and parse a key in a map as uint64.
    64  func GetMapKeyAsUint64(m map[string]string, key string, context apiObject, nonZero bool) (uint64, bool, error) {
    65  	if str, exists := m[key]; exists {
    66  		i, err := ParseUint64(str)
    67  		if err != nil {
    68  			return 0, exists, fmt.Errorf("%s %v/%v '%s' contains invalid uint64: %w, ignoring", context.GetObjectKind().GroupVersionKind().Kind, context.GetNamespace(), context.GetName(), key, err)
    69  		}
    70  
    71  		if nonZero && i == 0 {
    72  			return 0, exists, fmt.Errorf("%s %v/%v '%s' must be greater than 0, ignoring", context.GetObjectKind().GroupVersionKind().Kind, context.GetNamespace(), context.GetName(), key)
    73  		}
    74  
    75  		return i, exists, nil
    76  	}
    77  
    78  	return 0, false, nil
    79  }
    80  
    81  // GetMapKeyAsStringSlice tries to find and parse a key in the map as string slice splitting it on delimiter.
    82  func GetMapKeyAsStringSlice(m map[string]string, key string, context apiObject, delimiter string) ([]string, bool, error) {
    83  	if str, exists := m[key]; exists {
    84  		slice := strings.Split(str, delimiter)
    85  		return slice, exists, nil
    86  	}
    87  
    88  	return nil, false, nil
    89  }
    90  
    91  // ParseLBMethod parses method and matches it to a corresponding load balancing method in NGINX. An error is returned if method is not valid.
    92  func ParseLBMethod(method string) (string, error) {
    93  	method = strings.TrimSpace(method)
    94  
    95  	if method == "round_robin" {
    96  		return "", nil
    97  	}
    98  
    99  	if strings.HasPrefix(method, "hash") {
   100  		method, err := validateHashLBMethod(method)
   101  		return method, err
   102  	}
   103  
   104  	if _, exists := nginxLBValidInput[method]; exists {
   105  		return method, nil
   106  	}
   107  
   108  	return "", fmt.Errorf("Invalid load balancing method: %q", method)
   109  }
   110  
   111  var nginxLBValidInput = map[string]bool{
   112  	"least_conn":            true,
   113  	"ip_hash":               true,
   114  	"random":                true,
   115  	"random two":            true,
   116  	"random two least_conn": true,
   117  }
   118  
   119  var nginxPlusLBValidInput = map[string]bool{
   120  	"least_conn":                      true,
   121  	"ip_hash":                         true,
   122  	"random":                          true,
   123  	"random two":                      true,
   124  	"random two least_conn":           true,
   125  	"random two least_time=header":    true,
   126  	"random two least_time=last_byte": true,
   127  	"least_time header":               true,
   128  	"least_time last_byte":            true,
   129  	"least_time header inflight":      true,
   130  	"least_time last_byte inflight":   true,
   131  }
   132  
   133  // ParseLBMethodForPlus parses method and matches it to a corresponding load balancing method in NGINX Plus. An error is returned if method is not valid.
   134  func ParseLBMethodForPlus(method string) (string, error) {
   135  	method = strings.TrimSpace(method)
   136  
   137  	if method == "round_robin" {
   138  		return "", nil
   139  	}
   140  
   141  	if strings.HasPrefix(method, "hash") {
   142  		method, err := validateHashLBMethod(method)
   143  		return method, err
   144  	}
   145  
   146  	if _, exists := nginxPlusLBValidInput[method]; exists {
   147  		return method, nil
   148  	}
   149  
   150  	return "", fmt.Errorf("Invalid load balancing method: %q", method)
   151  }
   152  
   153  func validateHashLBMethod(method string) (string, error) {
   154  	keyWords := strings.Split(method, " ")
   155  
   156  	if keyWords[0] == "hash" {
   157  		if len(keyWords) == 2 || (len(keyWords) == 3 && keyWords[2] == "consistent") {
   158  			return method, nil
   159  		}
   160  	}
   161  
   162  	return "", fmt.Errorf("invalid load balancing method: %q", method)
   163  }
   164  
   165  // ParseBool ensures that the string value is a valid bool
   166  func ParseBool(s string) (bool, error) {
   167  	return strconv.ParseBool(s)
   168  }
   169  
   170  // ParseInt ensures that the string value is a valid int
   171  func ParseInt(s string) (int, error) {
   172  	return strconv.Atoi(s)
   173  }
   174  
   175  // ParseInt64 ensures that the string value is a valid int64
   176  func ParseInt64(s string) (int64, error) {
   177  	return strconv.ParseInt(s, 10, 64)
   178  }
   179  
   180  // ParseUint64 ensures that the string value is a valid uint64
   181  func ParseUint64(s string) (uint64, error) {
   182  	return strconv.ParseUint(s, 10, 64)
   183  }
   184  
   185  // timeRegexp http://nginx.org/en/docs/syntax.html
   186  var timeRegexp = regexp.MustCompile(`^(\d+y)??\s*(\d+M)??\s*(\d+w)??\s*(\d+d)??\s*(\d+h)??\s*(\d+m)??\s*(\d+s?)??\s*(\d+ms)??$`)
   187  
   188  // ParseTime ensures that the string value in the annotation is a valid time.
   189  func ParseTime(s string) (string, error) {
   190  	if s == "" || strings.TrimSpace(s) == "" || !timeRegexp.MatchString(s) {
   191  		return "", errors.New("invalid time string")
   192  	}
   193  	units := timeRegexp.FindStringSubmatch(s)
   194  	years := units[1]
   195  	months := units[2]
   196  	weeks := units[3]
   197  	days := units[4]
   198  	hours := units[5]
   199  	mins := units[6]
   200  	secs := units[7]
   201  	if secs != "" && !strings.HasSuffix(secs, "s") {
   202  		secs = secs + "s"
   203  	}
   204  	millis := units[8]
   205  	return fmt.Sprintf("%s%s%s%s%s%s%s%s", years, months, weeks, days, hours, mins, secs, millis), nil
   206  }
   207  
   208  // OffsetFmt http://nginx.org/en/docs/syntax.html
   209  const OffsetFmt = `\d+[kKmMgG]?`
   210  
   211  var offsetRegexp = regexp.MustCompile("^" + OffsetFmt + "$")
   212  
   213  // ParseOffset ensures that the string value is a valid offset
   214  func ParseOffset(s string) (string, error) {
   215  	s = strings.TrimSpace(s)
   216  
   217  	if offsetRegexp.MatchString(s) {
   218  		return s, nil
   219  	}
   220  	return "", errors.New("Invalid offset string")
   221  }
   222  
   223  // SizeFmt http://nginx.org/en/docs/syntax.html
   224  const SizeFmt = `\d+[kKmM]?`
   225  
   226  var sizeRegexp = regexp.MustCompile("^" + SizeFmt + "$")
   227  
   228  // ParseSize ensures that the string value is a valid size
   229  func ParseSize(s string) (string, error) {
   230  	s = strings.TrimSpace(s)
   231  
   232  	if sizeRegexp.MatchString(s) {
   233  		return s, nil
   234  	}
   235  	return "", errors.New("Invalid size string")
   236  }
   237  
   238  // https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffers
   239  var proxyBuffersRegexp = regexp.MustCompile(`^\d+ \d+[kKmM]?$`)
   240  
   241  // ParseProxyBuffersSpec ensures that the string value is a valid proxy buffer spec
   242  func ParseProxyBuffersSpec(s string) (string, error) {
   243  	s = strings.TrimSpace(s)
   244  
   245  	if proxyBuffersRegexp.MatchString(s) {
   246  		return s, nil
   247  	}
   248  	return "", errors.New("Invalid proxy buffers string")
   249  }
   250  
   251  // ParsePortList ensures that the string is a comma-separated list of port numbers
   252  func ParsePortList(s string) ([]int, error) {
   253  	var ports []int
   254  	for _, value := range strings.Split(s, ",") {
   255  		port, err := parsePort(value)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  		ports = append(ports, port)
   260  	}
   261  	return ports, nil
   262  }
   263  
   264  func parsePort(value string) (int, error) {
   265  	port, err := strconv.ParseInt(value, 10, 16)
   266  	if err != nil {
   267  		return 0, fmt.Errorf("Unable to parse port as integer: %w", err)
   268  	}
   269  
   270  	if port <= 0 {
   271  		return 0, fmt.Errorf("Port number should be greater than zero: %q", port)
   272  	}
   273  
   274  	return int(port), nil
   275  }
   276  
   277  // ParseServiceList ensures that the string is a comma-separated list of services
   278  func ParseServiceList(s string) map[string]bool {
   279  	services := make(map[string]bool)
   280  	for _, part := range strings.Split(s, ",") {
   281  		services[part] = true
   282  	}
   283  	return services
   284  }
   285  
   286  // ParseRewriteList ensures that the string is a semicolon-separated list of services
   287  func ParseRewriteList(s string) (map[string]string, error) {
   288  	rewrites := make(map[string]string)
   289  	for _, part := range strings.Split(s, ";") {
   290  		serviceName, rewrite, err := parseRewrites(part)
   291  		if err != nil {
   292  			return nil, err
   293  		}
   294  		rewrites[serviceName] = rewrite
   295  	}
   296  	return rewrites, nil
   297  }
   298  
   299  // ParseStickyServiceList ensures that the string is a semicolon-separated list of sticky services
   300  func ParseStickyServiceList(s string) (map[string]string, error) {
   301  	services := make(map[string]string)
   302  	for _, part := range strings.Split(s, ";") {
   303  		serviceName, service, err := parseStickyService(part)
   304  		if err != nil {
   305  			return nil, err
   306  		}
   307  		services[serviceName] = service
   308  	}
   309  	return services, nil
   310  }
   311  
   312  func parseStickyService(service string) (serviceName string, stickyCookie string, err error) {
   313  	parts := strings.SplitN(service, " ", 2)
   314  
   315  	if len(parts) != 2 {
   316  		return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s", service)
   317  	}
   318  
   319  	svcNameParts := strings.Split(parts[0], "=")
   320  	if len(svcNameParts) != 2 {
   321  		return "", "", fmt.Errorf("Invalid sticky-cookie service format: %s", svcNameParts)
   322  	}
   323  
   324  	return svcNameParts[1], parts[1], nil
   325  }
   326  
   327  func parseRewrites(service string) (serviceName string, rewrite string, err error) {
   328  	parts := strings.SplitN(strings.TrimSpace(service), " ", 2)
   329  
   330  	if len(parts) != 2 {
   331  		return "", "", fmt.Errorf("Invalid rewrite format: %s", service)
   332  	}
   333  
   334  	svcNameParts := strings.Split(parts[0], "=")
   335  	if len(svcNameParts) != 2 {
   336  		return "", "", fmt.Errorf("Invalid rewrite format: %s", svcNameParts)
   337  	}
   338  
   339  	rwPathParts := strings.Split(parts[1], "=")
   340  	if len(rwPathParts) != 2 {
   341  		return "", "", fmt.Errorf("Invalid rewrite format: %s", rwPathParts)
   342  	}
   343  
   344  	return svcNameParts[1], rwPathParts[1], nil
   345  }
   346  
   347  var (
   348  	threshEx  = regexp.MustCompile(`high=([1-9]|[1-9][0-9]|100) low=([1-9]|[1-9][0-9]|100)\b`)
   349  	threshExR = regexp.MustCompile(`low=([1-9]|[1-9][0-9]|100) high=([1-9]|[1-9][0-9]|100)\b`)
   350  )
   351  
   352  // VerifyAppProtectThresholds ensures that threshold values are set correctly
   353  func VerifyAppProtectThresholds(value string) bool {
   354  	return threshEx.MatchString(value) || threshExR.MatchString(value)
   355  }