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 }