2018-09-18 18:05:32 +00:00
/ *
Copyright 2018 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package canary
import (
2021-08-21 20:42:00 +00:00
networking "k8s.io/api/networking/v1"
2023-07-22 03:32:07 +00:00
"k8s.io/klog/v2"
2018-09-18 18:05:32 +00:00
"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/ingress/resolver"
)
2023-07-22 03:32:07 +00:00
const (
canaryAnnotation = "canary"
canaryWeightAnnotation = "canary-weight"
canaryWeightTotalAnnotation = "canary-weight-total"
canaryByHeaderAnnotation = "canary-by-header"
canaryByHeaderValueAnnotation = "canary-by-header-value"
canaryByHeaderPatternAnnotation = "canary-by-header-pattern"
canaryByCookieAnnotation = "canary-by-cookie"
)
var CanaryAnnotations = parser . Annotation {
Group : "canary" ,
Annotations : parser . AnnotationFields {
canaryAnnotation : {
Validator : parser . ValidateBool ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation enables the Ingress spec to act as an alternative service for requests to route to depending on the rules applied ` ,
} ,
canaryWeightAnnotation : {
Validator : parser . ValidateInt ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation defines the integer based (0 - ) percent of random requests that should be routed to the service specified in the canary Ingress ` ,
} ,
canaryWeightTotalAnnotation : {
Validator : parser . ValidateInt ,
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskLow ,
Documentation : ` This annotation The total weight of traffic. If unspecified, it defaults to 100 ` ,
} ,
canaryByHeaderAnnotation : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . BasicCharsRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the header that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress .
When the request header is set to ' always ' , it will be routed to the canary . When the header is set to ' never ' , it will never be routed to the canary .
For any other value , the header will be ignored and the request compared against the other canary rules by precedence ` ,
} ,
canaryByHeaderValueAnnotation : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . BasicCharsRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the header value to match for notifying the Ingress to route the request to the service specified in the Canary Ingress .
When the request header is set to this value , it will be routed to the canary . For any other header value , the header will be ignored and the request compared against the other canary rules by precedence .
This annotation has to be used together with ' canary - by - header ' . The annotation is an extension of the ' canary - by - header ' to allow customizing the header value instead of using hardcoded values .
It doesn ' t have any effect if the ' canary - by - header ' annotation is not defined ` ,
} ,
canaryByHeaderPatternAnnotation : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . IsValidRegex , false ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation works the same way as canary - by - header - value except it does PCRE Regex matching .
Note that when ' canary - by - header - value ' is set this annotation will be ignored .
When the given Regex causes error during request processing , the request will be considered as not matching . ` ,
} ,
canaryByCookieAnnotation : {
2023-08-31 07:36:48 +00:00
Validator : parser . ValidateRegex ( parser . BasicCharsRegex , true ) ,
2023-07-22 03:32:07 +00:00
Scope : parser . AnnotationScopeIngress ,
Risk : parser . AnnotationRiskMedium ,
Documentation : ` This annotation defines the cookie that should be used for notifying the Ingress to route the request to the service specified in the Canary Ingress .
When the cookie is set to ' always ' , it will be routed to the canary . When the cookie is set to ' never ' , it will never be routed to the canary ` ,
} ,
} ,
}
2018-09-18 18:05:32 +00:00
type canary struct {
2023-07-22 03:32:07 +00:00
r resolver . Resolver
annotationConfig parser . Annotation
2018-09-18 18:05:32 +00:00
}
// Config returns the configuration rules for setting up the Canary
type Config struct {
2020-02-19 04:21:08 +00:00
Enabled bool
Weight int
2021-12-07 16:40:00 +00:00
WeightTotal int
2020-02-19 04:21:08 +00:00
Header string
HeaderValue string
HeaderPattern string
Cookie string
2018-09-18 18:05:32 +00:00
}
// NewParser parses the ingress for canary related annotations
func NewParser ( r resolver . Resolver ) parser . IngressAnnotation {
2023-07-22 03:32:07 +00:00
return canary {
r : r ,
annotationConfig : CanaryAnnotations ,
}
2018-09-18 18:05:32 +00:00
}
// Parse parses the annotations contained in the ingress
// rule used to indicate if the canary should be enabled and with what config
2019-06-09 22:49:59 +00:00
func ( c canary ) Parse ( ing * networking . Ingress ) ( interface { } , error ) {
2018-09-18 18:05:32 +00:00
config := & Config { }
var err error
2023-07-22 03:32:07 +00:00
config . Enabled , err = parser . GetBoolAnnotation ( canaryAnnotation , ing , c . annotationConfig . Annotations )
2018-09-18 18:05:32 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to 'false'" , canaryAnnotation )
}
2018-09-18 18:05:32 +00:00
config . Enabled = false
}
2023-07-22 03:32:07 +00:00
config . Weight , err = parser . GetIntAnnotation ( canaryWeightAnnotation , ing , c . annotationConfig . Annotations )
2018-09-18 18:05:32 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to '0'" , canaryWeightAnnotation )
}
2018-09-18 18:05:32 +00:00
config . Weight = 0
}
2023-07-22 03:32:07 +00:00
config . WeightTotal , err = parser . GetIntAnnotation ( canaryWeightTotalAnnotation , ing , c . annotationConfig . Annotations )
2021-12-07 16:40:00 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to '100'" , canaryWeightTotalAnnotation )
}
2021-12-07 16:40:00 +00:00
config . WeightTotal = 100
}
2023-07-22 03:32:07 +00:00
config . Header , err = parser . GetStringAnnotation ( canaryByHeaderAnnotation , ing , c . annotationConfig . Annotations )
2018-09-18 18:05:32 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to ''" , canaryByHeaderAnnotation )
}
2018-09-18 18:05:32 +00:00
config . Header = ""
}
2023-07-22 03:32:07 +00:00
config . HeaderValue , err = parser . GetStringAnnotation ( canaryByHeaderValueAnnotation , ing , c . annotationConfig . Annotations )
2019-01-30 21:19:19 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to ''" , canaryByHeaderValueAnnotation )
}
2019-01-30 21:19:19 +00:00
config . HeaderValue = ""
}
2023-07-22 03:32:07 +00:00
config . HeaderPattern , err = parser . GetStringAnnotation ( canaryByHeaderPatternAnnotation , ing , c . annotationConfig . Annotations )
2020-02-19 04:21:08 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to ''" , canaryByHeaderPatternAnnotation )
}
2020-02-19 04:21:08 +00:00
config . HeaderPattern = ""
}
2023-07-22 03:32:07 +00:00
config . Cookie , err = parser . GetStringAnnotation ( canaryByCookieAnnotation , ing , c . annotationConfig . Annotations )
2018-09-18 18:05:32 +00:00
if err != nil {
2023-07-22 03:32:07 +00:00
if errors . IsValidationError ( err ) {
klog . Warningf ( "%s is invalid, defaulting to ''" , canaryByCookieAnnotation )
}
2018-09-18 18:05:32 +00:00
config . Cookie = ""
}
2024-03-07 10:39:53 +00:00
if ! config . Enabled && ( config . Weight > 0 || config . Header != "" || config . HeaderValue != "" || config . Cookie != "" ||
config . HeaderPattern != "" ) {
2023-07-22 03:32:07 +00:00
return nil , errors . NewInvalidAnnotationConfiguration ( canaryAnnotation , "configured but not enabled" )
2018-09-18 18:05:32 +00:00
}
return config , nil
}
2023-07-22 03:32:07 +00:00
func ( c canary ) GetDocumentation ( ) parser . AnnotationFields {
return c . annotationConfig . Annotations
}
2023-08-31 07:36:48 +00:00
func ( c canary ) Validate ( anns map [ string ] string ) error {
maxrisk := parser . StringRiskToRisk ( c . r . GetSecurityConfiguration ( ) . AnnotationsRiskLevel )
2023-07-22 03:32:07 +00:00
return parser . CheckAnnotationRisk ( anns , maxrisk , CanaryAnnotations . Annotations )
}