From 1d0f9cf6bbc67580a60319c216e26a8dd5f5e824 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 22 Jun 2022 10:36:16 -0500 Subject: [PATCH 01/11] add additionalStableIngresses datatype Signed-off-by: Travis Perdue --- manifests/crds/rollout-crd.yaml | 4 ++++ manifests/install.yaml | 4 ++++ manifests/namespace-install.yaml | 4 ++++ pkg/apiclient/rollout/rollout.swagger.json | 7 +++++++ pkg/apis/api-rules/violation_exceptions.list | 1 + pkg/apis/rollouts/v1alpha1/types.go | 3 +++ pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go | 5 +++++ ui/src/models/rollout/generated/api.ts | 6 ++++++ 8 files changed, 34 insertions(+) diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index bda1fdd4fc..a295f43046 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -761,6 +761,10 @@ spec: additionalProperties: type: string type: object + additionalStableIngresses: + items: + type: string + type: array annotationPrefix: type: string stableIngress: diff --git a/manifests/install.yaml b/manifests/install.yaml index 00ff531b5c..d9bdc9cad1 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -11729,6 +11729,10 @@ spec: additionalProperties: type: string type: object + additionalStableIngresses: + items: + type: string + type: array annotationPrefix: type: string stableIngress: diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 3edf1348e4..f900efb5eb 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -11729,6 +11729,10 @@ spec: additionalProperties: type: string type: object + additionalStableIngresses: + items: + type: string + type: array annotationPrefix: type: string stableIngress: diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 384103913b..8028a3fbc5 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -1027,6 +1027,13 @@ "type": "string" }, "title": "+optional" + }, + "additionalStableIngresses": { + "type": "array", + "items": { + "type": "string" + }, + "title": "AdditionalStableIngresses refers to the names of `Ingress` resources in the same namespace as the `Rollout` in a multi ingress scenario\n+optional" } }, "title": "NginxTrafficRouting configuration for Nginx ingress controller to control traffic routing" diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index d9cc556c27..96ebd0bc27 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -25,6 +25,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,IstioVirtualService,TLSRoutes API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,KayentaMetric,Scopes API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,MetricResult,Measurements +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,NginxTrafficRouting,AdditionalStableIngresses API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutAnalysis,Args API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutAnalysis,DryRun API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutAnalysis,MeasurementRetention diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 04d45fddcd..f2fffbf9b8 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -398,6 +398,9 @@ type NginxTrafficRouting struct { StableIngress string `json:"stableIngress" protobuf:"bytes,2,opt,name=stableIngress"` // +optional AdditionalIngressAnnotations map[string]string `json:"additionalIngressAnnotations,omitempty" protobuf:"bytes,3,rep,name=additionalIngressAnnotations"` + // AdditionalStableIngresses refers to the names of `Ingress` resources in the same namespace as the `Rollout` in a multi ingress scenario + // +optional + AdditionalStableIngresses []string `json:"additionalStableIngresses,omitempty" protobuf:"bytes,4,rep,name=additionalStableIngresses"` } // IstioTrafficRouting configuration for Istio service mesh to enable fine grain configuration diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index e45cf0dba6..5a3739b95d 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -1632,6 +1632,11 @@ func (in *NginxTrafficRouting) DeepCopyInto(out *NginxTrafficRouting) { (*out)[key] = val } } + if in.AdditionalStableIngresses != nil { + in, out := &in.AdditionalStableIngresses, &out.AdditionalStableIngresses + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index 1b4336be76..8de7ed3865 100644 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -791,6 +791,12 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTraffi * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTrafficRouting */ additionalIngressAnnotations?: { [key: string]: string; }; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1NginxTrafficRouting + */ + additionalStableIngresses?: Array; } /** * From 22845f2ca761afd103a05e2102de134dc8d0d603 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 22 Jun 2022 10:50:18 -0500 Subject: [PATCH 02/11] add validation for additionalStableIngresses Signed-off-by: Travis Perdue --- .../validation/validation_references.go | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/pkg/apis/rollouts/validation/validation_references.go b/pkg/apis/rollouts/validation/validation_references.go index a4d5561f4a..55128f993b 100644 --- a/pkg/apis/rollouts/validation/validation_references.go +++ b/pkg/apis/rollouts/validation/validation_references.go @@ -218,23 +218,39 @@ func setArgValuePlaceHolder(Args []v1alpha1.Argument) { func ValidateIngress(rollout *v1alpha1.Rollout, ingress *ingressutil.Ingress) field.ErrorList { allErrs := field.ErrorList{} fldPath := field.NewPath("spec", "strategy", "canary", "trafficRouting") + canary := rollout.Spec.Strategy.Canary var ingressName string var serviceName string - if rollout.Spec.Strategy.Canary.TrafficRouting.Nginx != nil { + if canary.TrafficRouting.Nginx != nil { + // If there are additional stable ingresses + if len(canary.TrafficRouting.Nginx.AdditionalStableIngresses) > 0 { + // validate each ingress as valid + fldPath = fldPath.Child("nginx").Child("additionalStableIngresses") + serviceName = canary.StableService + for _, ing := range canary.TrafficRouting.Nginx.AdditionalStableIngresses { + ingressName = ing + allErrs = reportErrors(ingress, serviceName, ingressName, fldPath, allErrs) + } + } fldPath = fldPath.Child("nginx").Child("stableIngress") - serviceName = rollout.Spec.Strategy.Canary.StableService - ingressName = rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress - } else if rollout.Spec.Strategy.Canary.TrafficRouting.ALB != nil { + serviceName = canary.StableService + ingressName = canary.TrafficRouting.Nginx.StableIngress + + allErrs = reportErrors(ingress, serviceName, ingressName, fldPath, allErrs) + } else if canary.TrafficRouting.ALB != nil { fldPath = fldPath.Child("alb").Child("ingress") - ingressName = rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress - serviceName = rollout.Spec.Strategy.Canary.StableService - if rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService != "" { - serviceName = rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService + ingressName = canary.TrafficRouting.ALB.Ingress + serviceName = canary.StableService + if canary.TrafficRouting.ALB.RootService != "" { + serviceName = canary.TrafficRouting.ALB.RootService } - - } else { - return allErrs + allErrs = reportErrors(ingress, serviceName, ingressName, fldPath, allErrs) } + + return allErrs +} + +func reportErrors(ingress *ingressutil.Ingress, serviceName, ingressName string, fldPath *field.Path, allErrs field.ErrorList) field.ErrorList { if !ingressutil.HasRuleWithService(ingress, serviceName) { msg := fmt.Sprintf("ingress `%s` has no rules using service %s backend", ingress.GetName(), serviceName) allErrs = append(allErrs, field.Invalid(fldPath, ingressName, msg)) From 066ed1e2e00f562147c9f48748813bf10b222063 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 22 Jun 2022 11:01:43 -0500 Subject: [PATCH 03/11] add rollout controller logic for additionalStableIngresses Signed-off-by: Travis Perdue --- rollout/controller.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rollout/controller.go b/rollout/controller.go index 827c7a7a20..c0434e726a 100644 --- a/rollout/controller.go +++ b/rollout/controller.go @@ -780,6 +780,19 @@ func (c *rolloutContext) getReferencedIngresses() (*[]ingressutil.Ingress, error } ingresses = append(ingresses, *ingress) } else if canary.TrafficRouting.Nginx != nil { + // If the rollout resource manages more than 1 ingress + if len(canary.TrafficRouting.Nginx.AdditionalStableIngresses) > 0 { + for _, ing := range canary.TrafficRouting.Nginx.AdditionalStableIngresses { + ingress, err := c.ingressWrapper.GetCached(c.rollout.Namespace, ing) + if k8serrors.IsNotFound(err) { + return nil, field.Invalid(fldPath.Child("nginx", "AdditionalStableIngresses"), canary.TrafficRouting.Nginx.StableIngress, err.Error()) + } + if err != nil { + return nil, err + } + ingresses = append(ingresses, *ingress) + } + } ingress, err := c.ingressWrapper.GetCached(c.rollout.Namespace, canary.TrafficRouting.Nginx.StableIngress) if k8serrors.IsNotFound(err) { return nil, field.Invalid(fldPath.Child("nginx", "stableIngress"), canary.TrafficRouting.Nginx.StableIngress, err.Error()) From e3024292848d7ae304e7930a796eac0cd7229087 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 22 Jun 2022 14:48:55 -0500 Subject: [PATCH 04/11] add nginx ingress logic for setting weight & fetching names for additionalStableIngresses Signed-off-by: Travis Perdue --- rollout/trafficrouting/nginx/nginx.go | 150 ++++++++++++++------------ utils/ingress/ingress.go | 32 ++++-- 2 files changed, 107 insertions(+), 75 deletions(-) diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index a0d291c121..b278a182ff 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -222,88 +222,106 @@ func (r *Reconciler) canaryIngress(stableIngress *ingressutil.Ingress, name stri // SetWeight modifies Nginx Ingress resources to reach desired state func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) error { - ctx := context.TODO() - stableIngressName := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress - canaryIngressName := ingressutil.GetCanaryIngressName(r.cfg.Rollout) - - // Check if stable ingress exists (from lister, which has a cache), error if it does not - stableIngress, err := r.cfg.IngressWrapper.GetCached(r.cfg.Rollout.Namespace, stableIngressName) - if err != nil { - r.log.WithField(logutil.IngressKey, stableIngressName).WithField("err", err.Error()).Error("error retrieving stableIngress") - return fmt.Errorf("error retrieving stableIngress `%s` from cache: %v", stableIngressName, err) - } - // Check if canary ingress exists (from lister which has a cache), determines whether we later call Create() or Update() - canaryIngress, err := r.cfg.IngressWrapper.GetCached(r.cfg.Rollout.Namespace, canaryIngressName) - - canaryIngressExists := true - if err != nil { - if !k8serrors.IsNotFound(err) { - // An error other than "not found" occurred - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error retrieving canary ingress") - return fmt.Errorf("error retrieving canary ingress `%s` from cache: %v", canaryIngressName, err) + // Set weight for additional ingresses if present + if ingresses := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses; ingresses != nil { + // Fail out if there is an issue setting weight on additional ingresesses. + // Fundamental assumption is that each additional Ingress is equal in importance + // as primary Ingress resource. + if err := r.SetWeightPerIngress(desiredWeight, ingresses); err != nil { + return err } - r.log.WithField(logutil.IngressKey, canaryIngressName).Infof("canary ingress not found") - canaryIngressExists = false - } - // Construct the desired canary Ingress resource - desiredCanaryIngress, err := r.canaryIngress(stableIngress, canaryIngressName, desiredWeight) - if err != nil { - r.log.WithField(logutil.IngressKey, canaryIngressName).Error(err.Error()) - return err } - if !canaryIngressExists { - r.cfg.Recorder.Eventf(r.cfg.Rollout, record.EventOptions{EventReason: "CreatingCanaryIngress"}, "Creating canary ingress `%s` with weight `%d`", canaryIngressName, desiredWeight) - _, err = r.cfg.IngressWrapper.Create(ctx, r.cfg.Rollout.Namespace, desiredCanaryIngress, metav1.CreateOptions{}) - if err == nil { - return nil + return r.SetWeightPerIngress(desiredWeight, []string{r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress}) +} + +// SetWeightMultiIngress modifies each Nginx Ingress resource to reach desired state in the scenario of a rollout +// having multiple Ngnix Ingress resources. +func (r *Reconciler) SetWeightPerIngress(desiredWeight int32, ingresses []string) error { + for _, ingress := range ingresses { + ctx := context.TODO() + stableIngressName := ingress + canaryIngressName := ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), stableIngressName) + + // Check if stable ingress exists (from lister, which has a cache), error if it does not + stableIngress, err := r.cfg.IngressWrapper.GetCached(r.cfg.Rollout.Namespace, stableIngressName) + if err != nil { + r.log.WithField(logutil.IngressKey, stableIngressName).WithField("err", err.Error()).Error("error retrieving stableIngress") + return fmt.Errorf("error retrieving stableIngress `%s` from cache: %v", stableIngressName, err) } - if !k8serrors.IsAlreadyExists(err) { - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error creating canary ingress") - return fmt.Errorf("error creating canary ingress `%s`: %v", canaryIngressName, err) + // Check if canary ingress exists (from lister which has a cache), determines whether we later call Create() or Update() + canaryIngress, err := r.cfg.IngressWrapper.GetCached(r.cfg.Rollout.Namespace, canaryIngressName) + + canaryIngressExists := true + if err != nil { + if !k8serrors.IsNotFound(err) { + // An error other than "not found" occurred + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error retrieving canary ingress") + return fmt.Errorf("error retrieving canary ingress `%s` from cache: %v", canaryIngressName, err) + } + r.log.WithField(logutil.IngressKey, canaryIngressName).Infof("canary ingress not found") + canaryIngressExists = false } - // Canary ingress was created by a different reconcile call before this one could complete (race) - // This means we just read it from the API now (instead of cache) and continue with the normal - // flow we take when the canary already existed. - canaryIngress, err = r.cfg.IngressWrapper.Get(ctx, r.cfg.Rollout.Namespace, canaryIngressName, metav1.GetOptions{}) + + // Construct the desired canary Ingress resource + desiredCanaryIngress, err := r.canaryIngress(stableIngress, canaryIngressName, desiredWeight) if err != nil { r.log.WithField(logutil.IngressKey, canaryIngressName).Error(err.Error()) - return fmt.Errorf("error retrieving canary ingress `%s` from api: %v", canaryIngressName, err) + return err + } + + if !canaryIngressExists { + r.cfg.Recorder.Eventf(r.cfg.Rollout, record.EventOptions{EventReason: "CreatingCanaryIngress"}, "Creating canary ingress `%s` with weight `%d`", canaryIngressName, desiredWeight) + _, err = r.cfg.IngressWrapper.Create(ctx, r.cfg.Rollout.Namespace, desiredCanaryIngress, metav1.CreateOptions{}) + if err == nil { + return nil + } + if !k8serrors.IsAlreadyExists(err) { + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error creating canary ingress") + return fmt.Errorf("error creating canary ingress `%s`: %v", canaryIngressName, err) + } + // Canary ingress was created by a different reconcile call before this one could complete (race) + // This means we just read it from the API now (instead of cache) and continue with the normal + // flow we take when the canary already existed. + canaryIngress, err = r.cfg.IngressWrapper.Get(ctx, r.cfg.Rollout.Namespace, canaryIngressName, metav1.GetOptions{}) + if err != nil { + r.log.WithField(logutil.IngressKey, canaryIngressName).Error(err.Error()) + return fmt.Errorf("error retrieving canary ingress `%s` from api: %v", canaryIngressName, err) + } } - } - // Canary Ingress already exists, apply a patch if needed + // Canary Ingress already exists, apply a patch if needed - // Only modify canaryIngress if it is controlled by this Rollout - if !metav1.IsControlledBy(canaryIngress.GetObjectMeta(), r.cfg.Rollout) { - r.log.WithField(logutil.IngressKey, canaryIngressName).Error("canary ingress controlled by different object") - return fmt.Errorf("canary ingress `%s` controlled by different object", canaryIngressName) - } + // Only modify canaryIngress if it is controlled by this Rollout + if !metav1.IsControlledBy(canaryIngress.GetObjectMeta(), r.cfg.Rollout) { + r.log.WithField(logutil.IngressKey, canaryIngressName).Error("canary ingress controlled by different object") + return fmt.Errorf("canary ingress `%s` controlled by different object", canaryIngressName) + } - // Make patches - patch, modified, err := ingressutil.BuildIngressPatch(canaryIngress.Mode(), canaryIngress, - desiredCanaryIngress, ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + // Make patches + patch, modified, err := ingressutil.BuildIngressPatch(canaryIngress.Mode(), canaryIngress, + desiredCanaryIngress, ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) - if err != nil { - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error constructing canary ingress patch") - return fmt.Errorf("error constructing canary ingress patch for `%s`: %v", canaryIngressName, err) - } - if !modified { - r.log.WithField(logutil.IngressKey, canaryIngressName).Info("No changes to canary ingress - skipping patch") - return nil - } + if err != nil { + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error constructing canary ingress patch") + return fmt.Errorf("error constructing canary ingress patch for `%s`: %v", canaryIngressName, err) + } + if !modified { + r.log.WithField(logutil.IngressKey, canaryIngressName).Info("No changes to canary ingress - skipping patch") + return nil + } - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("patch", string(patch)).Debug("applying canary Ingress patch") - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("desiredWeight", desiredWeight).Info("updating canary Ingress") - r.cfg.Recorder.Eventf(r.cfg.Rollout, record.EventOptions{EventReason: "PatchingCanaryIngress"}, "Updating Ingress `%s` to desiredWeight '%d'", canaryIngressName, desiredWeight) + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("patch", string(patch)).Debug("applying canary Ingress patch") + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("desiredWeight", desiredWeight).Info("updating canary Ingress") + r.cfg.Recorder.Eventf(r.cfg.Rollout, record.EventOptions{EventReason: "PatchingCanaryIngress"}, "Updating Ingress `%s` to desiredWeight '%d'", canaryIngressName, desiredWeight) - _, err = r.cfg.IngressWrapper.Patch(ctx, r.cfg.Rollout.Namespace, canaryIngressName, types.MergePatchType, patch, metav1.PatchOptions{}) - if err != nil { - r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error patching canary ingress") - return fmt.Errorf("error patching canary ingress `%s`: %v", canaryIngressName, err) + _, err = r.cfg.IngressWrapper.Patch(ctx, r.cfg.Rollout.Namespace, canaryIngressName, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + r.log.WithField(logutil.IngressKey, canaryIngressName).WithField("err", err.Error()).Error("error patching canary ingress") + return fmt.Errorf("error patching canary ingress `%s`: %v", canaryIngressName, err) + } } - return nil } diff --git a/utils/ingress/ingress.go b/utils/ingress/ingress.go index 42dc61ca20..d5bcb99d4d 100644 --- a/utils/ingress/ingress.go +++ b/utils/ingress/ingress.go @@ -62,13 +62,31 @@ func GetRolloutIngressKeys(rollout *v1alpha1.Rollout) []string { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx != nil && rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress != "" { + stableIngress := rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress // Also start watcher for `-canary` ingress which is created by the trafficmanagement controller ingresses = append( ingresses, - fmt.Sprintf("%s/%s", rollout.Namespace, rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), - fmt.Sprintf("%s/%s", rollout.Namespace, GetCanaryIngressName(rollout)), + fmt.Sprintf("%s/%s", rollout.Namespace, stableIngress), + fmt.Sprintf("%s/%s", rollout.Namespace, GetCanaryIngressName(rollout.GetName(), stableIngress)), ) } + + // Scenario where one rollout is managing multiple Ngnix ingresses. + if rollout.Spec.Strategy.Canary != nil && + rollout.Spec.Strategy.Canary.TrafficRouting != nil && + rollout.Spec.Strategy.Canary.TrafficRouting.Nginx != nil && + len(rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses) > 0 { + + for _, stableIngress := range rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses { + // Also start watcher for `-canary` ingress which is created by the trafficmanagement controller + ingresses = append( + ingresses, + fmt.Sprintf("%s/%s", rollout.Namespace, stableIngress), + fmt.Sprintf("%s/%s", rollout.Namespace, GetCanaryIngressName(rollout.GetName(), stableIngress)), + ) + } + } + if rollout.Spec.Strategy.Canary != nil && rollout.Spec.Strategy.Canary.TrafficRouting != nil && rollout.Spec.Strategy.Canary.TrafficRouting.ALB != nil && @@ -83,14 +101,10 @@ func GetRolloutIngressKeys(rollout *v1alpha1.Rollout) []string { } // GetCanaryIngressName constructs the name to use for the canary ingress resource from a given Rollout -func GetCanaryIngressName(rollout *v1alpha1.Rollout) string { +func GetCanaryIngressName(rolloutName, stableIngressName string) string { // names limited to 253 characters - if rollout.Spec.Strategy.Canary != nil && - rollout.Spec.Strategy.Canary.TrafficRouting != nil && - rollout.Spec.Strategy.Canary.TrafficRouting.Nginx != nil && - rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress != "" { - - prefix := fmt.Sprintf("%s-%s", rollout.GetName(), rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress) + if stableIngressName != "" { + prefix := fmt.Sprintf("%s-%s", rolloutName, stableIngressName) if len(prefix) > 253-len(CanaryIngressSuffix) { // trim prefix prefix = prefix[0 : 253-len(CanaryIngressSuffix)] From 31eb2e41a2d4e9d1c0bcf3edd6787d8f7688b363 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Wed, 22 Jun 2022 16:20:42 -0500 Subject: [PATCH 05/11] add nginx testing for additionalStableIngresses Signed-off-by: Travis Perdue --- rollout/trafficrouting/nginx/nginx_test.go | 600 ++++++++++++++++++++- utils/ingress/ingress_test.go | 6 +- 2 files changed, 597 insertions(+), 9 deletions(-) diff --git a/rollout/trafficrouting/nginx/nginx_test.go b/rollout/trafficrouting/nginx/nginx_test.go index 2b3a7cf328..d0037574c9 100644 --- a/rollout/trafficrouting/nginx/nginx_test.go +++ b/rollout/trafficrouting/nginx/nginx_test.go @@ -137,6 +137,12 @@ func fakeRollout(stableSvc, canarySvc, stableIng string) *v1alpha1.Rollout { } } +func fakeRolloutWithMultiIngress(stableSvc, canarySvc, stableIng, addStableIng string) *v1alpha1.Rollout { + rollout := fakeRollout(stableSvc, canarySvc, stableIng) + rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses = []string{addStableIng} + return rollout +} + func checkBackendService(t *testing.T, ing *ingressutil.Ingress, serviceName string) { t.Helper() switch ing.Mode() { @@ -188,7 +194,7 @@ func TestCanaryIngressCreate(t *testing.T) { stableIngress.Spec.IngressClassName = pointer.StringPtr("nginx-ext") i := ingressutil.NewLegacyIngress(stableIngress) - desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 10) + desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 10) assert.Nil(t, err, "No error returned when calling canaryIngress") checkBackendService(t, desiredCanaryIngress, "canary-service") @@ -203,6 +209,45 @@ func TestCanaryIngressCreate(t *testing.T) { assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) } +func TestCanaryIngressCreateMultiIngress(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + stableIngress.Spec.IngressClassName = pointer.StringPtr("nginx-ext") + i := ingressutil.NewLegacyIngress(stableIngress) + + desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 10) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, desiredCanaryIngress, "canary-service") + desired, err := desiredCanaryIngress.GetExtensionsIngress() + if err != nil { + t.Error(err) + t.FailNow() + } + assert.Equal(t, "true", desired.Annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "10", desired.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.NotNil(t, desired.Spec.IngressClassName) + assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) + + additionalDesiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 10) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, additionalDesiredCanaryIngress, "canary-service") + desired, err = additionalDesiredCanaryIngress.GetExtensionsIngress() + if err != nil { + t.Error(err) + t.FailNow() + } + assert.Equal(t, "true", desired.Annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "10", desired.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.NotNil(t, desired.Spec.IngressClassName) + assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) +} + func TestCanaryIngressPatchWeight(t *testing.T) { r := Reconciler{ cfg: ReconcilerConfig{ @@ -218,7 +263,7 @@ func TestCanaryIngressPatchWeight(t *testing.T) { stableIngress := ingressutil.NewLegacyIngress(stable) canaryIngress := ingressutil.NewLegacyIngress(canary) - desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 15) + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) assert.Nil(t, err, "No error returned when calling canaryIngress") checkBackendService(t, desiredCanaryIngress, "canary-service") @@ -230,6 +275,53 @@ func TestCanaryIngressPatchWeight(t *testing.T) { assert.Equal(t, "{\"metadata\":{\"annotations\":{\"nginx.ingress.kubernetes.io/canary-weight\":\"15\"}}}", string(patch), "compareCanaryIngresses returns expected patch") } +func TestCanaryIngressPatchWeightMultiIngress(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + stable := extensionsIngress("stable-ingress", 80, "stable-service") + canary := extensionsIngress("canary-ingress", 80, "canary-service") + canary.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "10", + }) + stableIngress := ingressutil.NewLegacyIngress(stable) + canaryIngress := ingressutil.NewLegacyIngress(canary) + + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, desiredCanaryIngress, "canary-service") + + patch, modified, err := ingressutil.BuildIngressPatch(canaryIngress.Mode(), canaryIngress, desiredCanaryIngress, + ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + assert.Nil(t, err, "compareCanaryIngresses returns no error") + assert.True(t, modified, "compareCanaryIngresses returns modified=true") + assert.Equal(t, "{\"metadata\":{\"annotations\":{\"nginx.ingress.kubernetes.io/canary-weight\":\"15\"}}}", string(patch), "compareCanaryIngresses returns expected patch") + + addStable := extensionsIngress("additional-stable-ingress", 80, "stable-service") + addCanary := extensionsIngress("additional-canary-ingress", 80, "canary-service") + addCanary.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "10", + }) + addStableIngress := ingressutil.NewLegacyIngress(addStable) + addCanaryIngress := ingressutil.NewLegacyIngress(addCanary) + + addDesiredCanaryIngress, err := r.canaryIngress(addStableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 15) + assert.Nil(t, err, "No error returned when calling addCanaryIngress") + + checkBackendService(t, addDesiredCanaryIngress, "canary-service") + + patch, modified, err = ingressutil.BuildIngressPatch(addCanaryIngress.Mode(), addCanaryIngress, addDesiredCanaryIngress, + ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + assert.Nil(t, err, "compareCanaryIngresses returns no error") + assert.True(t, modified, "compareCanaryIngresses returns modified=true") + assert.Equal(t, "{\"metadata\":{\"annotations\":{\"nginx.ingress.kubernetes.io/canary-weight\":\"15\"}}}", string(patch), "compareCanaryIngresses returns expected patch") +} + func TestCanaryIngressUpdatedRoute(t *testing.T) { r := Reconciler{ cfg: ReconcilerConfig{ @@ -246,7 +338,36 @@ func TestCanaryIngressUpdatedRoute(t *testing.T) { stableIngress := ingressutil.NewLegacyIngress(stable) canaryIngress := ingressutil.NewLegacyIngress(canary) - desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 15) + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, desiredCanaryIngress, "canary-service") + + patch, modified, err := ingressutil.BuildIngressPatch(canaryIngress.Mode(), canaryIngress, desiredCanaryIngress, + ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + assert.Nil(t, err, "compareCanaryIngresses returns no error") + assert.True(t, modified, "compareCanaryIngresses returns modified=true") + assert.Equal(t, "{\"spec\":{\"rules\":[{\"host\":\"fakehost.example.com\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"canary-service\",\"servicePort\":80},\"path\":\"/bar\"}]}}]}}", string(patch), "compareCanaryIngresses returns expected patch") +} + +func TestCanaryIngressUpdatedRouteMultiIngress(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + + stable := extensionsIngress("stable-ingress", 80, "stable-service") + stable.Spec.Rules[0].HTTP.Paths[0].Path = "/bar" + canary := extensionsIngress("canary-ingress", 80, "canary-service") + canary.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + stableIngress := ingressutil.NewLegacyIngress(stable) + canaryIngress := ingressutil.NewLegacyIngress(canary) + + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) assert.Nil(t, err, "No error returned when calling canaryIngress") checkBackendService(t, desiredCanaryIngress, "canary-service") @@ -256,6 +377,27 @@ func TestCanaryIngressUpdatedRoute(t *testing.T) { assert.Nil(t, err, "compareCanaryIngresses returns no error") assert.True(t, modified, "compareCanaryIngresses returns modified=true") assert.Equal(t, "{\"spec\":{\"rules\":[{\"host\":\"fakehost.example.com\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"canary-service\",\"servicePort\":80},\"path\":\"/bar\"}]}}]}}", string(patch), "compareCanaryIngresses returns expected patch") + + addStable := extensionsIngress("stable-ingress", 80, "stable-service") + addStable.Spec.Rules[0].HTTP.Paths[0].Path = "/bar" + addCanary := extensionsIngress("canary-ingress", 80, "canary-service") + addCanary.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + addStableIngress := ingressutil.NewLegacyIngress(addStable) + addCanaryIngress := ingressutil.NewLegacyIngress(addCanary) + + addDesiredCanaryIngress, err := r.canaryIngress(addStableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, addDesiredCanaryIngress, "canary-service") + + patch, modified, err = ingressutil.BuildIngressPatch(addCanaryIngress.Mode(), addCanaryIngress, addDesiredCanaryIngress, + ingressutil.WithAnnotations(), ingressutil.WithLabels(), ingressutil.WithSpec()) + assert.Nil(t, err, "compareCanaryIngresses returns no error") + assert.True(t, modified, "compareCanaryIngresses returns modified=true") + assert.Equal(t, "{\"spec\":{\"rules\":[{\"host\":\"fakehost.example.com\",\"http\":{\"paths\":[{\"backend\":{\"serviceName\":\"canary-service\",\"servicePort\":80},\"path\":\"/bar\"}]}}]}}", string(patch), "compareCanaryIngresses returns expected patch") } func TestCanaryIngressRetainIngressClass(t *testing.T) { @@ -270,7 +412,7 @@ func TestCanaryIngressRetainIngressClass(t *testing.T) { }) stableIngress := ingressutil.NewLegacyIngress(stable) - desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 15) + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) assert.Nil(t, err, "No error returned when calling canaryIngress") checkBackendService(t, desiredCanaryIngress, "canary-service") @@ -281,6 +423,45 @@ func TestCanaryIngressRetainIngressClass(t *testing.T) { assert.Equal(t, "nginx-foo", annotations["kubernetes.io/ingress.class"], "ingress-class annotation retained") } +func TestCanaryIngressRetainIngressClassMultiIngress(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + stable := extensionsIngress("stable-ingress", 80, "stable-service") + stable.SetAnnotations(map[string]string{ + "kubernetes.io/ingress.class": "nginx-foo", + }) + stableIngress := ingressutil.NewLegacyIngress(stable) + + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, desiredCanaryIngress, "canary-service") + + annotations := desiredCanaryIngress.GetAnnotations() + assert.Equal(t, "true", annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "15", annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.Equal(t, "nginx-foo", annotations["kubernetes.io/ingress.class"], "ingress-class annotation retained") + + addStable := extensionsIngress("stable-ingress", 80, "stable-service") + addStable.SetAnnotations(map[string]string{ + "kubernetes.io/ingress.class": "nginx-foo", + }) + addStableIngress := ingressutil.NewLegacyIngress(addStable) + + addDesiredCanaryIngress, err := r.canaryIngress(addStableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, addDesiredCanaryIngress, "canary-service") + + annotations = addDesiredCanaryIngress.GetAnnotations() + assert.Equal(t, "true", annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "15", annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.Equal(t, "nginx-foo", annotations["kubernetes.io/ingress.class"], "ingress-class annotation retained") +} + func TestCanaryIngressAdditionalAnnotations(t *testing.T) { r := Reconciler{ cfg: ReconcilerConfig{ @@ -294,7 +475,33 @@ func TestCanaryIngressAdditionalAnnotations(t *testing.T) { stable := extensionsIngress("stable-ingress", 80, "stable-service") stableIngress := ingressutil.NewLegacyIngress(stable) - desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 15) + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, desiredCanaryIngress, "canary-service") + + annotations := desiredCanaryIngress.GetAnnotations() + assert.Equal(t, "true", annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "15", annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.Equal(t, "X-Foo", annotations["nginx.ingress.kubernetes.io/canary-by-header"], "canary-by-header annotation set") + assert.Equal(t, "DoCanary", annotations["nginx.ingress.kubernetes.io/canary-by-header-value"], "canary-by-header-value annotation set") +} + +func TestCanaryIngressAdditionalAnnotationsMultiIngress(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalIngressAnnotations = map[string]string{ + "canary-by-header": "X-Foo", + "canary-by-header-value": "DoCanary", + } + + stable := extensionsIngress("stable-ingress", 80, "stable-service") + stableIngress := ingressutil.NewLegacyIngress(stable) + + desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 15) assert.Nil(t, err, "No error returned when calling canaryIngress") checkBackendService(t, desiredCanaryIngress, "canary-service") @@ -304,6 +511,20 @@ func TestCanaryIngressAdditionalAnnotations(t *testing.T) { assert.Equal(t, "15", annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") assert.Equal(t, "X-Foo", annotations["nginx.ingress.kubernetes.io/canary-by-header"], "canary-by-header annotation set") assert.Equal(t, "DoCanary", annotations["nginx.ingress.kubernetes.io/canary-by-header-value"], "canary-by-header-value annotation set") + + addStable := extensionsIngress("additional-stable-ingress", 80, "stable-service") + addStableIngress := ingressutil.NewLegacyIngress(addStable) + + addDesiredCanaryIngress, err := r.canaryIngress(addStableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 15) + assert.Nil(t, err, "No error returned when calling canaryIngress") + + checkBackendService(t, addDesiredCanaryIngress, "canary-service") + + annotations = addDesiredCanaryIngress.GetAnnotations() + assert.Equal(t, "true", annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "15", annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.Equal(t, "X-Foo", annotations["nginx.ingress.kubernetes.io/canary-by-header"], "canary-by-header annotation set") + assert.Equal(t, "DoCanary", annotations["nginx.ingress.kubernetes.io/canary-by-header-value"], "canary-by-header-value annotation set") } func TestReconciler_canaryIngress(t *testing.T) { @@ -320,7 +541,38 @@ func TestReconciler_canaryIngress(t *testing.T) { i := ingressutil.NewIngress(stableIngress) // when - desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 10) + desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 10) + + // then + assert.Nil(t, err, "No error returned when calling canaryIngress") + checkBackendService(t, desiredCanaryIngress, "canary-service") + desired, err := desiredCanaryIngress.GetNetworkingIngress() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "true", desired.Annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "10", desired.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.NotNil(t, desired.Spec.IngressClassName) + assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) + }) +} + +func TestReconciler_canaryIngressWithMultiIngress(t *testing.T) { + t.Run("will build desired networking ingress successfully", func(t *testing.T) { + // given + t.Parallel() + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress"), + }, + } + + stableIngress := networkingIngress("stable-ingress", 80, "stable-service") + stableIngress.Spec.IngressClassName = pointer.StringPtr("nginx-ext") + i := ingressutil.NewIngress(stableIngress) + + // when + desiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress), 10) // then assert.Nil(t, err, "No error returned when calling canaryIngress") @@ -333,6 +585,25 @@ func TestReconciler_canaryIngress(t *testing.T) { assert.Equal(t, "10", desired.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") assert.NotNil(t, desired.Spec.IngressClassName) assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) + + addStableIngress := networkingIngress("additional-stable-ingress", 80, "stable-service") + addStableIngress.Spec.IngressClassName = pointer.StringPtr("nginx-ext") + i = ingressutil.NewIngress(addStableIngress) + + // when + addDesiredCanaryIngress, err := r.canaryIngress(i, ingressutil.GetCanaryIngressName(r.cfg.Rollout.GetName(), r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses[0]), 10) + + // then + assert.Nil(t, err, "No error returned when calling canaryIngress") + checkBackendService(t, desiredCanaryIngress, "canary-service") + desired, err = addDesiredCanaryIngress.GetNetworkingIngress() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "true", desired.Annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") + assert.Equal(t, "10", desired.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.NotNil(t, desired.Spec.IngressClassName) + assert.Equal(t, "nginx-ext", *desired.Spec.IngressClassName) }) } @@ -398,6 +669,41 @@ func TestReconcileStableIngressFound(t *testing.T) { } } +func TestReconcileStableIngressFoundMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "stable-service") + + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + err = r.SetWeight(10) + assert.Nil(t, err, "Reconcile returns no error") + actions := client.Actions() + assert.Len(t, actions, 2) + if !t.Failed() { + // Avoid "index out of range" errors + assert.Equal(t, "create", actions[0].GetVerb(), "action: create canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[0].GetResource(), "action: create canary ingress") + + assert.Equal(t, "create", actions[1].GetVerb(), "action: create canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[1].GetResource(), "action: create canary ingress") + } +} + func TestReconcileStableIngressFoundWrongBackend(t *testing.T) { rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "other-service") @@ -422,6 +728,34 @@ func TestReconcileStableIngressFoundWrongBackend(t *testing.T) { assert.Contains(t, err.Error(), "has no rules using service", "correct error is returned") } +func TestReconcileStableIngressFoundWrongBackendMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + // this one will work + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + // This is the one that should error out + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "other-service") + + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + err = r.SetWeight(10) + assert.NotNil(t, err, "Reconcile returns error") + assert.Contains(t, err.Error(), "has no rules using service", "correct error is returned") +} + func TestReconcileStableAndCanaryIngressFoundNoOwner(t *testing.T) { rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") @@ -511,6 +845,54 @@ func TestReconcileStableAndCanaryIngressFoundPatch(t *testing.T) { } } +func TestReconcileStableAndCanaryIngressFoundPatchMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "stable-service") + canaryIngress := extensionsIngress("rollout-stable-ingress-canary", 80, "canary-service") + canaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + addCanaryIngress := extensionsIngress("rollout-additional-stable-ingress-canary", 80, "canary-service") + addCanaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + setIngressOwnerRef(canaryIngress, rollout) + setIngressOwnerRef(addCanaryIngress, rollout) + client := fake.NewSimpleClientset(canaryIngress, addCanaryIngress) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(canaryIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addCanaryIngress) + + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + err = r.SetWeight(10) + assert.Nil(t, err, "Reconcile returns no error") + actions := client.Actions() + assert.Len(t, actions, 2) + if !t.Failed() { + // Avoid "index out of range" errors + assert.Equal(t, "patch", actions[0].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[0].GetResource(), "action: patch canary ingress") + assert.Equal(t, "patch", actions[1].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[1].GetResource(), "action: patch canary ingress") + } +} + func TestReconcileWillInvokeNetworkingIngress(t *testing.T) { // given rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") @@ -551,6 +933,57 @@ func TestReconcileWillInvokeNetworkingIngress(t *testing.T) { } } +func TestReconcileWillInvokeNetworkingIngressMultiIngress(t *testing.T) { + // given + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := networkingIngress("stable-ingress", 80, "stable-service") + addStableIngress := networkingIngress("additional-stable-ingress", 80, "stable-service") + canaryIngress := networkingIngress("rollout-stable-ingress-canary", 80, "canary-service") + canaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + addCanaryIngress := networkingIngress("rollout-additional-stable-ingress-canary", 80, "canary-service") + addCanaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + canaryIngress.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(rollout, schema.GroupVersionKind{Group: "argoproj.io", Version: "v1alpha1", Kind: "Rollout"})}) + addCanaryIngress.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(rollout, schema.GroupVersionKind{Group: "argoproj.io", Version: "v1alpha1", Kind: "Rollout"})}) + client := fake.NewSimpleClientset(stableIngress, canaryIngress, addStableIngress, addCanaryIngress) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Networking().V1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Networking().V1().Ingresses().Informer().GetIndexer().Add(canaryIngress) + k8sI.Networking().V1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + k8sI.Networking().V1().Ingresses().Informer().GetIndexer().Add(addCanaryIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeNetworking, client, k8sI) + if err != nil { + t.Fatal(err) + } + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + // when + err = r.SetWeight(10) + + // then + assert.Nil(t, err, "Reconcile returns no error") + actions := client.Actions() + assert.Len(t, actions, 2) + if !t.Failed() { + // Avoid "index out of range" errors + assert.Equal(t, "patch", actions[0].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}, actions[0].GetResource(), "action: patch canary ingress") + assert.Equal(t, "patch", actions[1].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "networking.k8s.io", Version: "v1", Resource: "ingresses"}, actions[1].GetResource(), "action: patch canary ingress") + } +} + func TestReconcileStableAndCanaryIngressFoundNoChange(t *testing.T) { rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") @@ -582,6 +1015,46 @@ func TestReconcileStableAndCanaryIngressFoundNoChange(t *testing.T) { assert.Len(t, actions, 0) } +func TestReconcileStableAndCanaryIngressFoundNoChangeMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "stable-service") + canaryIngress := extensionsIngress("rollout-stable-ingress-canary", 80, "canary-service") + addCanaryIngress := extensionsIngress("rollout-additional-stable-ingress-canary", 80, "canary-service") + setIngressOwnerRef(canaryIngress, rollout) + setIngressOwnerRef(addCanaryIngress, rollout) + canaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "10", + }) + addCanaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "10", + }) + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(canaryIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addCanaryIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + err = r.SetWeight(10) + assert.Nil(t, err, "Reconcile returns no error") + actions := client.Actions() + assert.Len(t, actions, 0) +} + func TestReconcileCanaryCreateError(t *testing.T) { rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") @@ -622,6 +1095,48 @@ func TestReconcileCanaryCreateError(t *testing.T) { } } +func TestReconcileCanaryCreateErrorMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "stable-service") + + client := fake.NewSimpleClientset() + client.ReactionChain = nil + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + + // stableIngress exists + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + // Return with AlreadyExists error to create for canary + r.cfg.Client.(*fake.Clientset).Fake.AddReactor("create", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("fake error") + }) + + err = r.SetWeight(10) + assert.NotNil(t, err, "Reconcile returns error") + assert.Equal(t, "error creating canary ingress `rollout-additional-stable-ingress-canary`: fake error", err.Error()) + actions := client.Actions() + assert.Len(t, actions, 1) + if !t.Failed() { + // Avoid "index out of range" errors + assert.Equal(t, "create", actions[0].GetVerb(), "action: create canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[0].GetResource(), "action: create canary ingress") + } +} + func TestReconcileCanaryCreateErrorAlreadyExistsPatch(t *testing.T) { rollout := fakeRollout("stable-service", "canary-service", "stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") @@ -678,3 +1193,76 @@ func TestReconcileCanaryCreateErrorAlreadyExistsPatch(t *testing.T) { assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[2].GetResource(), "action: patch canary ingress") } } + +func TestReconcileCanaryCreateErrorAlreadyExistsPatchMultiIngress(t *testing.T) { + rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") + stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") + addStableIngress := extensionsIngress("additional-stable-ingress", 80, "stable-service") + canaryIngress := extensionsIngress("rollout-stable-ingress-canary", 80, "canary-service") + addCanaryIngress := extensionsIngress("rollout-additional-stable-ingress-canary", 80, "canary-service") + canaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + addCanaryIngress.SetAnnotations(map[string]string{ + "nginx.ingress.kubernetes.io/canary": "true", + "nginx.ingress.kubernetes.io/canary-weight": "15", + }) + setIngressOwnerRef(canaryIngress, rollout) + setIngressOwnerRef(addCanaryIngress, rollout) + + client := fake.NewSimpleClientset() + client.ReactionChain = nil + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + + // stableIngress exists + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(stableIngress) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(addStableIngress) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + + r := NewReconciler(ReconcilerConfig{ + Rollout: rollout, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + + // Return with AlreadyExists error to create for canary + r.cfg.Client.(*fake.Clientset).Fake.AddReactor("create", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, k8serrors.NewAlreadyExists(schema.GroupResource{ + Group: "extensions", + Resource: "ingresses", + }, "rollout-stable-ingress-canary") + }) + + // Respond with canaryIngress on GET + r.cfg.Client.(*fake.Clientset).Fake.AddReactor("get", "ingresses", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) { + return true, canaryIngress, nil + }) + + err = r.SetWeight(10) + assert.Nil(t, err, "Reconcile returns no error") + actions := client.Actions() + assert.Len(t, actions, 6) + if !t.Failed() { + // Avoid "index out of range" errors + // primary ingress + assert.Equal(t, "create", actions[0].GetVerb(), "action: create canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[0].GetResource(), "action: create canary ingress") + assert.Equal(t, "get", actions[1].GetVerb(), "action: get canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[1].GetResource(), "action: get canary ingress") + assert.Equal(t, "patch", actions[2].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[2].GetResource(), "action: patch canary ingress") + // additional ingress + assert.Equal(t, "create", actions[3].GetVerb(), "action: create canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[0].GetResource(), "action: create canary ingress") + assert.Equal(t, "get", actions[4].GetVerb(), "action: get canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[1].GetResource(), "action: get canary ingress") + assert.Equal(t, "patch", actions[5].GetVerb(), "action: patch canary ingress") + assert.Equal(t, schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "ingresses"}, actions[2].GetResource(), "action: patch canary ingress") + } +} diff --git a/utils/ingress/ingress_test.go b/utils/ingress/ingress_test.go index eaca7e6a86..c545cd0b2d 100644 --- a/utils/ingress/ingress_test.go +++ b/utils/ingress/ingress_test.go @@ -84,18 +84,18 @@ func TestGetCanaryIngressName(t *testing.T) { t.Run("NoTrim", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress = "stable-ingress" - canaryIngress := GetCanaryIngressName(rollout) + canaryIngress := GetCanaryIngressName(rollout.GetName(), rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress) assert.Equal(t, "myrollout-stable-ingress-canary", canaryIngress) }) t.Run("Trim", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress = fmt.Sprintf("stable-ingress%s", strings.Repeat("a", 260)) - canaryIngress := GetCanaryIngressName(rollout) + canaryIngress := GetCanaryIngressName(rollout.GetName(), rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress) assert.Equal(t, 253, len(canaryIngress), "canary ingress truncated to 253") assert.Equal(t, true, strings.HasSuffix(canaryIngress, "-canary"), "canary ingress has -canary suffix") }) t.Run("NoStableIngress", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx = nil - canaryIngress := GetCanaryIngressName(rollout) + canaryIngress := GetCanaryIngressName(rollout.GetName(), "") assert.Equal(t, "", canaryIngress, "canary ingress is empty") }) } From 56880f0191a6acc5dbd89d0e59eefab25df45fc5 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 23 Jun 2022 10:19:37 -0500 Subject: [PATCH 06/11] add ingress & controller tests for additionalStableIngresses Signed-off-by: Travis Perdue --- ingress/ingress_test.go | 112 +++++++++++++++++- .../validation/validation_references_test.go | 18 +++ rollout/controller_test.go | 91 ++++++++++++++ 3 files changed, 219 insertions(+), 2 deletions(-) diff --git a/ingress/ingress_test.go b/ingress/ingress_test.go index b26b213e25..679eba81f6 100644 --- a/ingress/ingress_test.go +++ b/ingress/ingress_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" kubeinformers "k8s.io/client-go/informers" k8sfake "k8s.io/client-go/kubernetes/fake" @@ -54,7 +55,15 @@ func newNginxIngress(name string, port int, serviceName string) *extensionsv1bet } } +func newFakeIngressControllerMultiIngress(t *testing.T, ing []*extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) { + return underlyingControllerBuilder(t, ing, rollout) +} + func newFakeIngressController(t *testing.T, ing *extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) { + return underlyingControllerBuilder(t, []*extensionsv1beta1.Ingress{ing}, rollout) +} + +func underlyingControllerBuilder(t *testing.T, ing []*extensionsv1beta1.Ingress, rollout *v1alpha1.Rollout) (*Controller, *k8sfake.Clientset, map[string]int) { t.Helper() client := fake.NewSimpleClientset() if rollout != nil { @@ -62,7 +71,13 @@ func newFakeIngressController(t *testing.T, ing *extensionsv1beta1.Ingress, roll } kubeclient := k8sfake.NewSimpleClientset() if ing != nil { - kubeclient = k8sfake.NewSimpleClientset(ing) + var x []runtime.Object + for _, i := range ing { + if i != nil { + x = append(x, i) + } + } + kubeclient = k8sfake.NewSimpleClientset(x...) } i := informers.NewSharedInformerFactory(client, 0) k8sI := kubeinformers.NewSharedInformerFactory(kubeclient, 0) @@ -107,7 +122,11 @@ func newFakeIngressController(t *testing.T, ing *extensionsv1beta1.Ingress, roll } if ing != nil { - k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(ing) + for _, i := range ing { + if i != nil { + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + } + } } if rollout != nil { i.Argoproj().V1alpha1().Rollouts().Informer().GetIndexer().Add(rollout) @@ -133,6 +152,20 @@ func TestSyncIngressNotReferencedByRollout(t *testing.T) { assert.Len(t, actions, 0) } +func TestSyncIngressNotReferencedByRolloutMultiIngress(t *testing.T) { + ings := []*extensionsv1beta1.Ingress{ + newNginxIngress("test-stable-ingress", 80, "stable-service"), + newNginxIngress("test-stable-ingress-additional", 80, "stable-service"), + } + + ctrl, kubeclient, _ := newFakeIngressControllerMultiIngress(t, ings, nil) + + err := ctrl.syncIngress("default/test-stable-ingress") + assert.NoError(t, err) + actions := kubeclient.Actions() + assert.Len(t, actions, 0) +} + func TestSyncIngressReferencedByRollout(t *testing.T) { ing := newNginxIngress("test-stable-ingress", 80, "stable-service") @@ -165,6 +198,42 @@ func TestSyncIngressReferencedByRollout(t *testing.T) { assert.Equal(t, 1, enqueuedObjects["default/rollout"]) } +func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { + ings := []*extensionsv1beta1.Ingress{ + newNginxIngress("test-stable-ingress", 80, "stable-service"), + newNginxIngress("test-stable-ingress-additional", 80, "stable-service"), + } + + rollout := &v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rollout", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: "stable-service", + CanaryService: "canary-service", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "test-stable-ingress", + AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, + }, + }, + }, + }, + }, + } + + ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, ings, rollout) + + err := ctrl.syncIngress("default/test-stable-ingress") + assert.NoError(t, err) + actions := kubeclient.Actions() + assert.Len(t, actions, 0) + assert.Equal(t, 1, enqueuedObjects["default/rollout"]) +} + func TestSkipIngressWithNoAnnotations(t *testing.T) { ing := newNginxIngress("test-stable-ingress", 80, "stable-service") ing.Annotations = nil @@ -196,3 +265,42 @@ func TestSkipIngressWithNoAnnotations(t *testing.T) { assert.Len(t, actions, 0) assert.Len(t, enqueuedObjects, 0) } + +func TestSkipIngressWithNoAnnotationsMultiIngress(t *testing.T) { + ings := []*extensionsv1beta1.Ingress{ + newNginxIngress("test-stable-ingress", 80, "stable-service"), + newNginxIngress("test-stable-ingress-additional", 80, "stable-service"), + } + for _, i := range ings { + i.Annotations = nil + } + + rollout := &v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rollout", + Namespace: metav1.NamespaceDefault, + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: "stable-service", + CanaryService: "canary-service", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "test-stable-ingress", + AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, + }, + }, + }, + }, + }, + } + + ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, ings, rollout) + + err := ctrl.syncIngress("default/test-stable-ingress") + assert.NoError(t, err) + actions := kubeclient.Actions() + assert.Len(t, actions, 0) + assert.Len(t, enqueuedObjects, 0) +} diff --git a/pkg/apis/rollouts/validation/validation_references_test.go b/pkg/apis/rollouts/validation/validation_references_test.go index 7b14dd7483..1df7d45b25 100644 --- a/pkg/apis/rollouts/validation/validation_references_test.go +++ b/pkg/apis/rollouts/validation/validation_references_test.go @@ -181,6 +181,24 @@ func getRollout() *v1alpha1.Rollout { } } +func getRolloutMultiIngress() *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: "stable-service-name", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "test-stable-ingress", + AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, + }, + }, + }, + }, + }, + } +} + func getIngress() *v1beta1.Ingress { return &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 7f324ea10b..04424f491b 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -1660,6 +1660,8 @@ func TestGetReferencedIngressesNginx(t *testing.T) { defer f.Close() t.Run("get referenced Nginx ingress - fail", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} c, _, _ := f.newController(noResyncPeriodFunc) roCtx, err := c.newRolloutContext(r) assert.NoError(t, err) @@ -1669,6 +1671,8 @@ func TestGetReferencedIngressesNginx(t *testing.T) { }) t.Run("get referenced Nginx ingress - success", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} ingress := &extensionsv1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: "nginx-ingress-name", @@ -1683,6 +1687,93 @@ func TestGetReferencedIngressesNginx(t *testing.T) { assert.NoError(t, err) }) } +func TestGetReferencedIngressesNginxMultiIngress(t *testing.T) { + f := newFixture(t) + defer f.Close() + r := newCanaryRollout("rollout", 1, nil, nil, nil, intstr.FromInt(0), intstr.FromInt(1)) + r.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "nginx-ingress-name", + AdditionalStableIngresses: []string{"nginx-ingress-additional"}, + }, + } + r.Namespace = metav1.NamespaceDefault + defer f.Close() + + t.Run("get referenced Nginx ingress - fail on secondary when both missing", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + _, err = roCtx.getReferencedIngresses() + expectedErr := field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "nginx", "AdditionalStableIngresses"), "nginx-ingress-name", "ingress.extensions \"nginx-ingress-additional\" not found") + assert.Equal(t, expectedErr.Error(), err.Error()) + }) + + t.Run("get referenced Nginx ingress - fail on primary when additional present", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + ingressAdditional := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-ingress-additional", + Namespace: metav1.NamespaceDefault, + }, + } + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingressAdditional)) + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + _, err = roCtx.getReferencedIngresses() + expectedErr := field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "nginx", "stableIngress"), "nginx-ingress-name", "ingress.extensions \"nginx-ingress-name\" not found") + assert.Equal(t, expectedErr.Error(), err.Error()) + }) + + t.Run("get referenced Nginx ingress - fail on secondary when only secondary missing", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + ingress := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-ingress-name", + Namespace: metav1.NamespaceDefault, + }, + } + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingress)) + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + _, err = roCtx.getReferencedIngresses() + if err == nil { + fmt.Println("ERROR IS NIL") + } + expectedErr := field.Invalid(field.NewPath("spec", "strategy", "canary", "trafficRouting", "nginx", "AdditionalStableIngresses"), "nginx-ingress-name", "ingress.extensions \"nginx-ingress-additional\" not found") + assert.Equal(t, expectedErr.Error(), err.Error()) + }) + + t.Run("get referenced Nginx ingress - success", func(t *testing.T) { + // clear fixture + f.ingressLister = []*ingressutil.Ingress{} + ingress := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-ingress-name", + Namespace: metav1.NamespaceDefault, + }, + } + ingressAdditional := &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-ingress-additional", + Namespace: metav1.NamespaceDefault, + }, + } + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingress)) + f.ingressLister = append(f.ingressLister, ingressutil.NewLegacyIngress(ingressAdditional)) + c, _, _ := f.newController(noResyncPeriodFunc) + roCtx, err := c.newRolloutContext(r) + assert.NoError(t, err) + _, err = roCtx.getReferencedIngresses() + assert.NoError(t, err) + }) +} func TestGetReferencedAppMeshResources(t *testing.T) { r := newCanaryRollout("rollout", 1, nil, nil, nil, intstr.FromInt(0), intstr.FromInt(1)) From 1c45c187677e32d702d6b80a6fb8a25f968d9f87 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 23 Jun 2022 10:20:57 -0500 Subject: [PATCH 07/11] remove validation_references_test.go snafu Signed-off-by: Travis Perdue --- .../validation/validation_references_test.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pkg/apis/rollouts/validation/validation_references_test.go b/pkg/apis/rollouts/validation/validation_references_test.go index 1df7d45b25..7b14dd7483 100644 --- a/pkg/apis/rollouts/validation/validation_references_test.go +++ b/pkg/apis/rollouts/validation/validation_references_test.go @@ -181,24 +181,6 @@ func getRollout() *v1alpha1.Rollout { } } -func getRolloutMultiIngress() *v1alpha1.Rollout { - return &v1alpha1.Rollout{ - Spec: v1alpha1.RolloutSpec{ - Strategy: v1alpha1.RolloutStrategy{ - Canary: &v1alpha1.CanaryStrategy{ - StableService: "stable-service-name", - TrafficRouting: &v1alpha1.RolloutTrafficRouting{ - Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "test-stable-ingress", - AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, - }, - }, - }, - }, - }, - } -} - func getIngress() *v1beta1.Ingress { return &v1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ From 427576ce8d653aaeffd8295faa91699e3a7d2c44 Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Thu, 23 Jun 2022 10:43:06 -0500 Subject: [PATCH 08/11] add ingress util testing for additionalStableIngresses Signed-off-by: Travis Perdue --- utils/ingress/ingress_test.go | 46 +++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/utils/ingress/ingress_test.go b/utils/ingress/ingress_test.go index c545cd0b2d..eb82a4f967 100644 --- a/utils/ingress/ingress_test.go +++ b/utils/ingress/ingress_test.go @@ -61,6 +61,33 @@ func TestGetRolloutIngressKeysForCanaryWithTrafficRouting(t *testing.T) { assert.ElementsMatch(t, keys, []string{"default/stable-ingress", "default/myrollout-stable-ingress-canary", "default/alb-ingress"}) } +func TestGetRolloutIngressKeysForCanaryWithTrafficRoutingMultiIngress(t *testing.T) { + keys := GetRolloutIngressKeys(&v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myrollout", + Namespace: "default", + }, + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + CanaryService: "canary-service", + StableService: "stable-service", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "stable-ingress", + AdditionalStableIngresses: []string{"stable-ingress-additional"}, + }, + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "alb-ingress", + }, + }, + }, + }, + }, + }) + assert.ElementsMatch(t, keys, []string{"default/stable-ingress", "default/myrollout-stable-ingress-canary", "default/stable-ingress-additional", "default/myrollout-stable-ingress-additional-canary", "default/alb-ingress"}) +} + func TestGetCanaryIngressName(t *testing.T) { rollout := &v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ @@ -75,6 +102,7 @@ func TestGetCanaryIngressName(t *testing.T) { TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ StableIngress: "stable-ingress", + AdditionalStableIngresses: []string{"stable-ingress-additional"}, }, }, }, @@ -82,17 +110,31 @@ func TestGetCanaryIngressName(t *testing.T) { }, } - t.Run("NoTrim", func(t *testing.T) { + t.Run("StableIngress - NoTrim", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress = "stable-ingress" canaryIngress := GetCanaryIngressName(rollout.GetName(), rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress) assert.Equal(t, "myrollout-stable-ingress-canary", canaryIngress) }) - t.Run("Trim", func(t *testing.T) { + t.Run("StableIngress - Trim", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress = fmt.Sprintf("stable-ingress%s", strings.Repeat("a", 260)) canaryIngress := GetCanaryIngressName(rollout.GetName(), rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress) assert.Equal(t, 253, len(canaryIngress), "canary ingress truncated to 253") assert.Equal(t, true, strings.HasSuffix(canaryIngress, "-canary"), "canary ingress has -canary suffix") }) + t.Run("AdditionalStableIngresses - NoTrim", func(t *testing.T) { + for _, ing := range rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses { + canaryIngress := GetCanaryIngressName(rollout.GetName(), ing) + assert.Equal(t, "myrollout-stable-ingress-additional-canary", canaryIngress) + } + }) + t.Run("AdditionalStableIngresses - Trim", func(t *testing.T) { + rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses = []string{fmt.Sprintf("stable-ingress%s", strings.Repeat("a", 260))} + for _, ing := range rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.AdditionalStableIngresses { + canaryIngress := GetCanaryIngressName(rollout.GetName(), ing) + assert.Equal(t, 253, len(canaryIngress), "canary ingress truncated to 253") + assert.Equal(t, true, strings.HasSuffix(canaryIngress, "-canary"), "canary ingress has -canary suffix") + } + }) t.Run("NoStableIngress", func(t *testing.T) { rollout.Spec.Strategy.Canary.TrafficRouting.Nginx = nil canaryIngress := GetCanaryIngressName(rollout.GetName(), "") From c99628b2e4e24bec14a6c26be5e930fa8c19e66d Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 30 Aug 2022 15:13:51 -0500 Subject: [PATCH 09/11] Cut v1.2.1 rallyhealth (#3) * fix: Add pagination to FindLoadBalancerByDNSName (#1971) * fix: this close issue #1963 by adding pagination to FindLoadBalancerByDNSName This adds pagination to the FindLoadBalancerByDNSName function this should allow argo rollouts to work with any number of loadbalancers. Signed-off-by: zachaller * fix: missing lb event (#2021) * fix: turn missing load balancer log into an event Signed-off-by: zachaller * consistent naming Signed-off-by: zachaller * fix: Use actual weight from status field on rollout object (#1937) fix: Use actual weight from status field on rollout object (#1937) Signed-off-by: zachaller * fix: build/lint is broken due to dependencies changes (#1958) Signed-off-by: zachaller * following github workflow error prompt to fix Signed-off-by: Travis Perdue * go fmt Signed-off-by: Travis Perdue * make go-mod-vendor Signed-off-by: Travis Perdue * fix path Signed-off-by: Travis Perdue Signed-off-by: zachaller Signed-off-by: Travis Perdue Co-authored-by: Zach Aller Co-authored-by: Travis Perdue --- .github/workflows/go.yml | 8 ++------ go.mod | 2 +- go.sum | 4 ++-- ingress/ingress_test.go | 4 ++-- rollout/controller_test.go | 2 +- utils/ingress/ingress_test.go | 4 ++-- 6 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1e064f306f..2460360380 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,10 +11,6 @@ env: # Golang version to use across CI steps GOLANG_VERSION: '1.17' -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - jobs: lint-go: name: Lint Go code @@ -87,8 +83,8 @@ jobs: # This symlink is necessary to ensure that `git diff` detects changes - name: Create symlink in GOPATH run: | - mkdir -p ~/go/src/github.com/argoproj - ln -s $(pwd) ~/go/src/github.com/argoproj/argo-rollouts + mkdir -p ~/go/src/github.com/rallyhealth + ln -s $(pwd) ~/go/src/github.com/rallyhealth/argo-rollouts - uses: actions/cache@v2 with: path: /home/runner/.cache/go-build diff --git a/go.mod b/go.mod index 37bebd331e..0a0e391e9b 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/antonmedv/expr v1.9.0 github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec - github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 + github.com/argoproj/pkg v0.9.0 github.com/aws/aws-sdk-go-v2 v1.13.0 github.com/aws/aws-sdk-go-v2/config v1.13.1 github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.15.0 diff --git a/go.sum b/go.sum index 146e68a910..f45bf974f5 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec h1:ulv8ieYQZLyQrTVR4za1ucLFnemS0Dksz8y5e91xxak= github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec/go.mod h1:QF4tr3wfWOnhkKSaRpx7k/KEErQAh8iwKQ2pYFu/SfA= -github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc= -github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA= +github.com/argoproj/pkg v0.9.0 h1:PfWWYykfcEQdN0g41XLbVh/aonTjD+dPkvDp3hwpLYM= +github.com/argoproj/pkg v0.9.0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/ingress/ingress_test.go b/ingress/ingress_test.go index 679eba81f6..41461bbb02 100644 --- a/ingress/ingress_test.go +++ b/ingress/ingress_test.go @@ -216,7 +216,7 @@ func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { CanaryService: "canary-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "test-stable-ingress", + StableIngress: "test-stable-ingress", AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, }, }, @@ -287,7 +287,7 @@ func TestSkipIngressWithNoAnnotationsMultiIngress(t *testing.T) { CanaryService: "canary-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "test-stable-ingress", + StableIngress: "test-stable-ingress", AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, }, }, diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 04424f491b..99ecf967e1 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -1693,7 +1693,7 @@ func TestGetReferencedIngressesNginxMultiIngress(t *testing.T) { r := newCanaryRollout("rollout", 1, nil, nil, nil, intstr.FromInt(0), intstr.FromInt(1)) r.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "nginx-ingress-name", + StableIngress: "nginx-ingress-name", AdditionalStableIngresses: []string{"nginx-ingress-additional"}, }, } diff --git a/utils/ingress/ingress_test.go b/utils/ingress/ingress_test.go index eb82a4f967..a11fd5abc2 100644 --- a/utils/ingress/ingress_test.go +++ b/utils/ingress/ingress_test.go @@ -74,7 +74,7 @@ func TestGetRolloutIngressKeysForCanaryWithTrafficRoutingMultiIngress(t *testing StableService: "stable-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "stable-ingress", + StableIngress: "stable-ingress", AdditionalStableIngresses: []string{"stable-ingress-additional"}, }, ALB: &v1alpha1.ALBTrafficRouting{ @@ -101,7 +101,7 @@ func TestGetCanaryIngressName(t *testing.T) { StableService: "stable-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "stable-ingress", + StableIngress: "stable-ingress", AdditionalStableIngresses: []string{"stable-ingress-additional"}, }, }, From 93d05cdc2efa9e772c4257f734c302e6e60e0ca0 Mon Sep 17 00:00:00 2001 From: ssanders1449 <59558157+ssanders1449@users.noreply.github.com> Date: Tue, 27 Sep 2022 20:09:38 +0300 Subject: [PATCH 10/11] add v1.2.2 patch for issue#2024 (#5) Signed-off-by: Shlomo Sanders Signed-off-by: Shlomo Sanders Co-authored-by: Shlomo Sanders --- analysis/analysis.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/analysis/analysis.go b/analysis/analysis.go index 5bae4a8892..53d5358647 100644 --- a/analysis/analysis.go +++ b/analysis/analysis.go @@ -316,7 +316,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa for _, task := range tasks { wg.Add(1) - go func(t metricTask) error { + go func(t metricTask) { defer wg.Done() //redact secret values from logs logger := logutil.WithRedactor(*logutil.WithAnalysisRun(run).WithField("metric", t.metric.Name), secrets) @@ -326,9 +326,12 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa resultsLock.Unlock() provider, err := c.newProvider(*logger, t.metric) + //Fix for https://github.com/argoproj/argo-rollouts/issues/2024 this error is not bubbled to runMeasurements function + //it just stops the go routine to prevent nil pointer usage. Just keeping this simple due to it being a patch for a bug. + //We probably want to handle errors in this goroutine in a different way in master but for now just prevent crashing. if err != nil { log.Errorf("Error in getting provider :%v", err) - return err + return } if metricResult == nil { metricResult = &v1alpha1.MetricResult{ @@ -408,7 +411,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa resultsLock.Lock() analysisutil.SetResult(run, *metricResult) resultsLock.Unlock() - return nil + }(task) } wg.Wait() From a1af6602566d28dedcb859d409fee073995c5cbd Mon Sep 17 00:00:00 2001 From: Travis Perdue Date: Tue, 18 Oct 2022 09:18:40 -0500 Subject: [PATCH 11/11] V1.3.0 rallyhealth pr (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add support for influxdb as a metrics provider (#1839) * feat: add InfluxDB metric provider implementation Signed-off-by: Jayme Bird * feat: add influx to metric provider util count, add missing graphite Signed-off-by: Jayme Bird * feat: add protobuf definitions for InfluxdbMetric Signed-off-by: Jayme Bird * feat: add docs for the Influxdb metrics provider Signed-off-by: Jayme Bird * feat: add tests for influxdb metrics provider Signed-off-by: Jayme Bird * feat: add go mod replace for version of moq due to security issue Signed-off-by: Jayme Bird * feat: update error message to distinguish failed cases - review feedback Signed-off-by: Jayme Bird * feat: update tests to simplify mocking of QueryTableResult An upstream change to the influx go client makes it easier to mock QueryTableResult in tests. This removes a lot of boilerplate duplication in the tests. Signed-off-by: Jayme Bird * feat: re-run codegen Signed-off-by: Jayme Bird * feat: Adds support for Istio traffic mirroring (#2074) * feat: Add support for istio traffic mirroring steps Signed-off-by: zachaller * bump e2e timeout Signed-off-by: zachaller * Remove unintended change Signed-off-by: zachaller * Cleaner events Signed-off-by: zachaller * Add docs for mirror and fix up header routing Signed-off-by: zachaller * small doc changes Signed-off-by: zachaller * Add example Signed-off-by: zachaller * Fix types and change example Signed-off-by: zachaller * Remove unused type Signed-off-by: zachaller * Docs change Signed-off-by: zachaller * Fix PR comments Signed-off-by: zachaller * Check for >= Signed-off-by: zachaller * Remove unused function Signed-off-by: zachaller * codegen Signed-off-by: zachaller * Add better error logging Signed-off-by: zachaller * a few more error msgs Signed-off-by: zachaller * fix comments Signed-off-by: zachaller * typo Signed-off-by: zachaller * fix logic error Signed-off-by: zachaller * redo header logic Signed-off-by: zachaller * cleanup Signed-off-by: zachaller * Fix issue with keeping route weight updated when no routes are defined in spec.strategy.canary.trafficRouting.istio.virtualService.routes requiring only a single route Signed-off-by: zachaller * cleanup routes when promiting with last step being a pause Signed-off-by: zachaller * fix bug with promote and last step is a pause Signed-off-by: zachaller * Fix tests Signed-off-by: zachaller * fix typo Signed-off-by: zachaller * chore: improve openapi schema (#2081) * feat: include all CRDs to the OpenAPI schema Signed-off-by: Mário Bezerra * feat: use kube-openapi naming convention in the OpenAPI schema Signed-off-by: Mário Bezerra * chore: generate OpenAPI schema Signed-off-by: Mário Bezerra * feat(grafana): Allow selecting datasource for grafana dashboard (#1988) Signed-off-by: Jesse Antoszyk <22500761+jcantosz@users.noreply.github.com> * feat: support /rollouts/:namespace?q=... and /rollout/:namespace/:name (#1902) Signed-off-by: Simon Ninon * chore: Add e2e and unit test comment reports (#2123) Signed-off-by: zachaller * chore: upgrade deps (#2136) Signed-off-by: zachaller * fixes #2141 Added list and watch to clusterrole (#2145) Signed-off-by: Martin Adler <1208749+EagleIJoe@users.noreply.github.com> * fix: Update ro.Status.ALB when first creating rollout object (#1986) * fix: make sure we update rollout.Status.ALB when first create Rollout object This fixes the case when we first create a rollout the status.ALB field does not get updated with valid information form the elb. Signed-off-by: zachaller * Refactor check for if we should verify alb. This creates a new rolloututil function ShouldVerifyWeight that verifies that we should call aws because the rollout is in the middle of some update. The reason I feel this should move into the ingress implmentation function VerifyWeight is becuase in the future other ingresses might also need to verify weights and adding ingress specific status checks in trafficrouting.go like a leak details where it should be up to the ingress provider to determin if it should be careful of rate limiting do to say it being a cloud provider call. Signed-off-by: zachaller * lint Signed-off-by: zachaller * Fix tests Signed-off-by: zachaller * Add new test for ShouldVerifyWeight Signed-off-by: zachaller * Fix logic Signed-off-by: zachaller * Add test for where we do not need to verify weight and its already set Signed-off-by: zachaller * fix up review comments Signed-off-by: zachaller * fix tests Signed-off-by: zachaller * fix bad merge Signed-off-by: zachaller * Fix test Signed-off-by: zachaller * Fix test Signed-off-by: zachaller * Clearer function name Signed-off-by: zachaller * Clean up if logic Signed-off-by: zachaller * fix: remove metrics when objects are removed from cluster to prevent build up (#2115) * fix: remove metrics on resource removal Signed-off-by: zachaller * Add test Signed-off-by: zachaller * More tests Signed-off-by: zachaller * back off deps updates Signed-off-by: zachaller * Fix dep change Signed-off-by: zachaller * upgrade prom deps for new features Signed-off-by: zachaller * fix deps Signed-off-by: zachaller * chore: use controler-gen for cluster analysis template scope (#2148) Signed-off-by: zachaller * fix(analysis): Fix Analysis Terminal Decision For Dry-Run Metrics (#2131) Signed-off-by: Rohit Agrawal * docs: update release doc with brew formula details (#2165) Signed-off-by: Leonardo Luz Almeida * fix(docs) Graphite metrics provider linked in docs sidebar. Fixes #2102. (#2094) * Graphite metrics provider linked in docs sidebar This fixes an issue wherein the Graphite metrics provider has no sidebar link in the docs hosted at https://argoproj.github.io/argo-rollouts/. As a bonus, this also removes various trailing whitespace from analysis docs. Signed-off-by: Mike Ball * build trigger Signed-off-by: Mike Ball * feat: emit rollout delete event (#1893) Signed-off-by: Hui Kang Co-authored-by: Hui Kang * chore: Upgrade golang (#2160) * Upgrade golang Signed-off-by: zachaller * upgrade golang to 1.18 for e2e Signed-off-by: zachaller * Fix deps Signed-off-by: zachaller * Update build action Signed-off-by: zachaller * Upgrade push action Signed-off-by: zachaller * Update docker file Signed-off-by: zachaller * Bump golang lint to match Signed-off-by: zachaller * fix go.mod Signed-off-by: zachaller * fix: Failed to process: Object 'Kind' is missing in Errors with rollouts notification (#2150) * fix: update rolloutobject with gvk before writing to rollout informer Signed-off-by: Ravi Hari * fix: controller schema linting Signed-off-by: Ravi Hari * docs: comment the details on the change Signed-off-by: Ravi Hari * docs: comment the details on the change Signed-off-by: Ravi Hari * fix: rootPath support so that it uses the embedded files system (#2198) * fix: rootPath support so that it uses the embeded files system Signed-off-by: zachaller * Catch edge cases and make sure we always server index.html on not found Signed-off-by: zachaller * turn path.Clean into var Signed-off-by: zachaller Signed-off-by: zachaller * fix: change completed condition so it only triggers on pod hash changes also adds an event for when it does changes. (#2203) * feat: add healthy event/condition and fix completed event/condition Signed-off-by: zachaller * fix unit tests Signed-off-by: zachaller * rename vars to make more sense and remove healthy event becase it will never be consistent Signed-off-by: zachaller * fix unit tests Signed-off-by: zachaller * possible fix for e2e Signed-off-by: zachaller * fix e2e Signed-off-by: zachaller * unit test for complete function Signed-off-by: zachaller * small cleanup and changes to not check generation Signed-off-by: zachaller * fix unit test and proper behavior Signed-off-by: zachaller * Fix e2e Signed-off-by: zachaller * rename and fix one unit test Signed-off-by: zachaller * fix unit tests Signed-off-by: zachaller * fix unit test Signed-off-by: zachaller * add seperate test for TestRolloutComplete Signed-off-by: zachaller * renames Signed-off-by: zachaller * fix e2e Signed-off-by: zachaller * Set Completed to false Signed-off-by: zachaller * Add event Signed-off-by: zachaller * fix e2e Signed-off-by: zachaller * refactor Signed-off-by: zachaller * Fix all but one unit test Signed-off-by: zachaller * fix last unit test Signed-off-by: zachaller * lint Signed-off-by: zachaller * cleanup Signed-off-by: zachaller * Rename Signed-off-by: zachaller * More renames Signed-off-by: zachaller * small comment change Signed-off-by: zachaller Signed-off-by: zachaller * feat: Add support for spec.ingressClassName (#2178) This change adds support for `spec.ingressClassName` while still supporting `kubernetes.io/ingress.class` annotation for backwards compatibility. Fixes #1277 Signed-off-by: Siavash Safi Signed-off-by: Siavash Safi * fix: enable notifications without when condition (#2231) * fix: enable notifications without when condition Signed-off-by: Ravi Hari * fix: use trigger action item from the list Signed-off-by: Ravi Hari * fix: add emptycondition logic to make notifications work with/without conditions Signed-off-by: Ravi Hari * fix: linting Signed-off-by: Ravi Hari Signed-off-by: Ravi Hari * Use standard cli format for dashboard root path (#2244) Signed-off-by: zachaller Signed-off-by: zachaller * merge conflict broke tests Signed-off-by: Travis Perdue Signed-off-by: Jayme Bird Signed-off-by: zachaller Signed-off-by: Mário Bezerra Signed-off-by: Jesse Antoszyk <22500761+jcantosz@users.noreply.github.com> Signed-off-by: Simon Ninon Signed-off-by: Martin Adler <1208749+EagleIJoe@users.noreply.github.com> Signed-off-by: Rohit Agrawal Signed-off-by: Leonardo Luz Almeida Signed-off-by: Mike Ball Signed-off-by: Ravi Hari Signed-off-by: Siavash Safi Signed-off-by: Travis Perdue Co-authored-by: jaymebrd Co-authored-by: Zach Aller Co-authored-by: Mário Bezerra Co-authored-by: Jesse Antoszyk <22500761+jcantosz@users.noreply.github.com> Co-authored-by: Simon Ninon Co-authored-by: Martin Adler <1208749+EagleIJoe@users.noreply.github.com> Co-authored-by: Rohit Agrawal Co-authored-by: Leonardo Luz Almeida Co-authored-by: Mike Ball Co-authored-by: cskh Co-authored-by: Hui Kang Co-authored-by: RaviHari Co-authored-by: Siavash Safi --- .github/workflows/docker-publish.yml | 6 +- .github/workflows/e2e-test-results.yml | 43 + .github/workflows/e2e.yaml | 21 +- .github/workflows/gh-pages.yaml | 2 +- .github/workflows/go.yml | 23 +- .github/workflows/release.yaml | 2 +- Dockerfile | 6 +- Makefile | 19 +- README.md | 2 +- USERS.md | 1 + analysis/analysis_test.go | 8 +- analysis/controller.go | 11 +- controller/metrics/analysis_test.go | 8 +- controller/metrics/client_test.go | 4 +- controller/metrics/experiment_test.go | 6 +- controller/metrics/metrics.go | 47 + controller/metrics/metrics_test.go | 36 +- controller/metrics/rollout_test.go | 6 +- docs/analysis/cloudwatch.md | 2 +- docs/analysis/influxdb.md | 41 + docs/analysis/kayenta.md | 6 +- docs/analysis/newrelic.md | 2 +- docs/analysis/prometheus.md | 6 +- docs/analysis/wavefront.md | 1 - docs/analysis/web.md | 6 +- .../features/kustomize/rollout_cr_schema.json | 16647 +++++++++++++++- docs/features/specification.md | 58 +- docs/features/traffic-management/alb.md | 26 +- docs/features/traffic-management/index.md | 107 +- docs/features/traffic-management/nginx.md | 4 +- docs/index.md | 12 +- docs/releasing.md | 7 +- examples/dashboard.json | 61 +- examples/traffic-routing/istio-mirror.yaml | 117 + experiments/controller.go | 5 + go.mod | 249 +- go.sum | 684 +- hack/gen-crd-spec/main.go | 13 +- hack/installers/install-codegen-go-tools.sh | 2 +- hack/installers/install-dev-tools.sh | 24 + ingress/ingress.go | 7 +- ingress/ingress_test.go | 71 +- manifests/crds/analysis-run-crd.yaml | 10 + manifests/crds/analysis-template-crd.yaml | 10 + .../crds/cluster-analysis-template-crd.yaml | 10 + manifests/crds/experiment-crd.yaml | 3 + manifests/crds/rollout-crd.yaml | 60 +- manifests/dashboard-install.yaml | 2 + .../dashboard-clusterrole.yaml | 2 + manifests/install.yaml | 93 +- manifests/namespace-install.yaml | 93 +- metricproviders/influxdb/influxdb.go | 136 + metricproviders/influxdb/influxdb_test.go | 249 + metricproviders/influxdb/mock_test.go | 32 + metricproviders/metricproviders.go | 17 +- metricproviders/mocks/Provider.go | 17 +- metricproviders/prometheus/mock_test.go | 4 +- mkdocs.yml | 2 + pkg/apiclient/rollout/rollout.swagger.json | 459 +- pkg/apis/api-rules/violation_exceptions.list | 4 +- pkg/apis/rollouts/v1alpha1/analysis_types.go | 12 +- pkg/apis/rollouts/v1alpha1/generated.pb.go | 2421 ++- pkg/apis/rollouts/v1alpha1/generated.proto | 72 +- .../rollouts/v1alpha1/openapi_generated.go | 188 +- pkg/apis/rollouts/v1alpha1/types.go | 69 +- .../v1alpha1/zz_generated.deepcopy.go | 132 +- pkg/apis/rollouts/validation/validation.go | 101 +- .../rollouts/validation/validation_test.go | 168 +- pkg/client/clientset/versioned/clientset.go | 4 + .../cmd/dashboard/dashboard.go | 2 +- rollout/analysis_test.go | 81 +- rollout/bluegreen_test.go | 127 +- rollout/canary.go | 4 +- rollout/canary_test.go | 38 +- rollout/controller.go | 18 + rollout/controller_test.go | 76 +- rollout/experiment_test.go | 14 +- rollout/mocks/TrafficRoutingReconciler.go | 55 +- rollout/service_test.go | 45 +- rollout/sync.go | 47 +- rollout/trafficrouting.go | 75 +- rollout/trafficrouting/alb/alb.go | 29 +- rollout/trafficrouting/alb/alb_test.go | 97 + .../trafficrouting/ambassador/ambassador.go | 10 +- .../ambassador/ambassador_test.go | 99 + rollout/trafficrouting/appmesh/appmesh.go | 10 +- .../trafficrouting/appmesh/appmesh_test.go | 57 + rollout/trafficrouting/istio/istio.go | 571 +- rollout/trafficrouting/istio/istio_test.go | 514 +- rollout/trafficrouting/istio/istio_types.go | 24 +- rollout/trafficrouting/nginx/nginx.go | 10 +- rollout/trafficrouting/nginx/nginx_test.go | 39 + rollout/trafficrouting/smi/smi.go | 10 +- rollout/trafficrouting/smi/smi_test.go | 59 + rollout/trafficrouting/traefik/traefik.go | 10 +- .../trafficrouting/traefik/traefik_test.go | 55 + rollout/trafficrouting/trafficroutingutil.go | 8 +- rollout/trafficrouting_test.go | 48 +- server/server.go | 140 +- test/e2e/analysis_test.go | 1 + test/e2e/functional_test.go | 27 +- test/e2e/header-routing/istio-hr-host.yaml | 11 +- ...r_routing_test.go => header_route_test.go} | 12 +- test/e2e/mirror-route/istio-mirror-host.yaml | 124 + test/e2e/mirror_route_test.go | 102 + test/fixtures/e2e_suite.go | 6 +- test/fixtures/when.go | 30 +- ui/package.json | 1 + ui/src/app/App.tsx | 6 +- ui/src/app/components/header/header.tsx | 14 +- ui/src/app/components/rollout/rollout.tsx | 128 +- .../rollouts-list/rollouts-list.tsx | 13 +- ui/src/models/rollout/generated/api.ts | 137 +- utils/analysis/factory.go | 6 + utils/analysis/factory_test.go | 2 + utils/analysis/helpers.go | 4 +- utils/analysis/helpers_test.go | 14 + utils/aws/mocks/ELBv2APIClient.go | 17 +- utils/conditions/conditions.go | 25 +- utils/conditions/rollouts_test.go | 28 +- utils/defaults/defaults.go | 14 + utils/defaults/defaults_test.go | 4 + utils/ingress/wrapper.go | 21 + utils/ingress/wrapper_test.go | 87 + utils/record/record.go | 47 +- utils/record/record_test.go | 41 +- utils/replicaset/canary.go | 13 - utils/replicaset/canary_test.go | 36 - utils/rollout/rolloututil.go | 15 + utils/rollout/rolloututil_test.go | 18 + utils/unstructured/unstructured.go | 18 + utils/unstructured/unstructured_test.go | 58 +- 132 files changed, 24360 insertions(+), 1871 deletions(-) create mode 100644 .github/workflows/e2e-test-results.yml create mode 100644 docs/analysis/influxdb.md create mode 100644 examples/traffic-routing/istio-mirror.yaml create mode 100755 hack/installers/install-dev-tools.sh create mode 100644 metricproviders/influxdb/influxdb.go create mode 100644 metricproviders/influxdb/influxdb_test.go create mode 100644 metricproviders/influxdb/mock_test.go rename test/e2e/{header_routing_test.go => header_route_test.go} (85%) create mode 100644 test/e2e/mirror-route/istio-mirror-host.yaml create mode 100644 test/e2e/mirror_route_test.go diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index a7c8e55a75..0299f089a0 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -21,7 +21,7 @@ jobs: uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 with: config-inline: | [worker.oci] @@ -79,14 +79,14 @@ jobs: echo "::set-output name=platform-matrix::$PLATFORM_MATRIX" - name: Build and push (controller-image) - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: platforms: ${{ steps.platform-matrix.outputs.platform-matrix }} push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.controller-meta.outputs.tags }} - name: Build and push (plugin-image) - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: target: kubectl-argo-rollouts platforms: ${{ steps.platform-matrix.outputs.platform-matrix }} diff --git a/.github/workflows/e2e-test-results.yml b/.github/workflows/e2e-test-results.yml new file mode 100644 index 0000000000..7130f3503a --- /dev/null +++ b/.github/workflows/e2e-test-results.yml @@ -0,0 +1,43 @@ +name: Test Results + +on: + workflow_run: + workflows: ["E2E Tests", "Go"] + types: + - completed +permissions: {} + +jobs: + test-results: + name: "${{ github.event.workflow.name }} Test Results" + runs-on: ubuntu-latest + if: github.event.workflow_run.conclusion != 'skipped' + permissions: + checks: write + pull-requests: write + actions: read + steps: + - name: Download and Extract Artifacts + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + run: | + mkdir -p artifacts && cd artifacts + artifacts_url=${{ github.event.workflow_run.artifacts_url }} + gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact + do + IFS=$'\t' read name url <<< "$artifact" + gh api $url > "$name.zip" + unzip -d "$name" "$name.zip" + done + + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v1 + with: + check_name: "${{ github.event.workflow.name }} Published Test Results" + commit: ${{ github.event.workflow_run.head_sha }} + event_file: artifacts/Event File/event.json + event_name: ${{ github.event.workflow_run.event }} + files: "artifacts/**/*.xml" + compare_to_earlier_commit: false + test_changes_limit: 0 + fail_on: "errors" diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 43620453b6..8f933b5bbd 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -21,6 +21,15 @@ concurrency: cancel-in-progress: true jobs: + event_file: + name: "Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@v2 + with: + name: Event File + path: ${{ github.event_path }} test-e2e: name: Run end-to-end tests runs-on: ubuntu-latest @@ -28,7 +37,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - uses: actions/checkout@v2 - name: Setup k3s run: | @@ -58,6 +67,16 @@ jobs: - name: Run e2e tests run: make test-e2e if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled) }} + - name: Output Rerun Overview + run: | + [[ -f rerunreport.txt ]] && cat rerunreport.txt || echo "No rerun report found" + - name: Upload E2E Test Results + if: always() + uses: actions/upload-artifact@v2 + with: + name: E2E Test Results + path: | + junit.xml - name: Upload e2e-controller logs uses: actions/upload-artifact@v2 with: diff --git a/.github/workflows/gh-pages.yaml b/.github/workflows/gh-pages.yaml index 00dd5adaac..3b054e9d82 100644 --- a/.github/workflows/gh-pages.yaml +++ b/.github/workflows/gh-pages.yaml @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - name: build run: | pip install mkdocs mkdocs_material diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2460360380..66745e9b95 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -9,9 +9,18 @@ on: - "master" env: # Golang version to use across CI steps - GOLANG_VERSION: '1.17' + GOLANG_VERSION: '1.18' jobs: + event_file: + name: "Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@v2 + with: + name: Event File + path: ${{ github.event_path }} lint-go: name: Lint Go code runs-on: ubuntu-latest @@ -25,7 +34,7 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.45.2 + version: v1.47.2 args: --timeout 5m build: name: Build @@ -54,7 +63,15 @@ jobs: run: make controller plugin - name: Test - run: go test -failfast -covermode=count -coverprofile=coverage.out ./... + run: make test-unit + + - name: Upload Unit Test Results + if: always() + uses: actions/upload-artifact@v2 + with: + name: Unit Test Results + path: | + junit.xml - name: Generate code coverage artifacts uses: actions/upload-artifact@v2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5a2336a5f7..a5f37f1f8e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -106,7 +106,7 @@ jobs: - name: Setup Golang uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - name: Generate release artifacts run: | diff --git a/Dockerfile b/Dockerfile index 9865e51415..19553e355e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image # Also used as the image in CI jobs so needs all dependencies #################################################################################################### -FROM --platform=$BUILDPLATFORM golang:1.17 as builder +FROM --platform=$BUILDPLATFORM golang:1.18 as builder RUN apt-get update && apt-get install -y \ wget \ @@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Install golangci-lint -RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.44.0 && \ +RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.47.2 && \ golangci-lint linters COPY .golangci.yml ${GOPATH}/src/dummy/.golangci.yml @@ -40,7 +40,7 @@ RUN NODE_ENV='production' yarn build #################################################################################################### # Rollout Controller Build stage which performs the actual build of argo-rollouts binaries #################################################################################################### -FROM --platform=$BUILDPLATFORM golang:1.17 as argo-rollouts-build +FROM --platform=$BUILDPLATFORM golang:1.18 as argo-rollouts-build WORKDIR /go/src/github.com/argoproj/argo-rollouts diff --git a/Makefile b/Makefile index 3302b41c1d..df2542169b 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,8 @@ DEV_IMAGE=false # E2E variables E2E_INSTANCE_ID ?= argo-rollouts-e2e E2E_TEST_OPTIONS ?= -E2E_PARALLEL ?= 4 -E2E_WAIT_TIMEOUT ?= 90 +E2E_PARALLEL ?= 1 +E2E_WAIT_TIMEOUT ?= 120 override LDFLAGS += \ -X ${PACKAGE}/utils/version.version=${VERSION} \ @@ -79,9 +79,13 @@ install-go-tools-local: go-mod-vendor install-protoc-local: ./hack/installers/install-protoc.sh +.PHONY: install-devtools-local +install-devtools-local: + ./hack/installers/install-dev-tools.sh + # Installs all tools required to build and test locally .PHONY: install-tools-local -install-tools-local: install-go-tools-local install-protoc-local +install-tools-local: install-go-tools-local install-protoc-local install-devtools-local TYPES := $(shell find pkg/apis/rollouts/v1alpha1 -type f -name '*.go' -not -name openapi_generated.go -not -name '*generated*' -not -name '*test.go') APIMACHINERY_PKGS=k8s.io/apimachinery/pkg/util/intstr,+k8s.io/apimachinery/pkg/api/resource,+k8s.io/apimachinery/pkg/runtime/schema,+k8s.io/apimachinery/pkg/runtime,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/api/core/v1,k8s.io/api/batch/v1 @@ -212,8 +216,13 @@ start-e2e: go run ./cmd/rollouts-controller/main.go --instance-id ${E2E_INSTANCE_ID} --loglevel debug --kloglevel 6 .PHONY: test-e2e -test-e2e: - go test -timeout 30m -v -count 1 --tags e2e -p ${E2E_PARALLEL} --short ./test/e2e ${E2E_TEST_OPTIONS} +test-e2e: install-devtools-local + ${DIST_DIR}/gotestsum --rerun-fails-report=rerunreport.txt --junitfile=junit.xml --format=testname --packages="./test/e2e" --rerun-fails=5 -- -timeout 60m -count 1 --tags e2e -p ${E2E_PARALLEL} -parallel ${E2E_PARALLEL} -v --short ./test/e2e ${E2E_TEST_OPTIONS} + +.PHONY: test-unit + test-unit: install-devtools-local + ${DIST_DIR}/gotestsum --junitfile=junit.xml --format=testname --packages="./..." -- -covermode=count -coverprofile=coverage.out ./... + .PHONY: coverage coverage: test diff --git a/README.md b/README.md index 94931ac5b5..f044877ab3 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ For these reasons, in large scale high-volume production environments, a rolling * Customizable metric queries and analysis of business KPIs * Ingress controller integration: NGINX, ALB * Service Mesh integration: Istio, Linkerd, SMI -* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic +* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic, InfluxDB ## Documentation To learn more about Argo Rollouts go to the [complete documentation](https://argoproj.github.io/argo-rollouts/). diff --git a/USERS.md b/USERS.md index cc59fd3382..9d7d511bbe 100644 --- a/USERS.md +++ b/USERS.md @@ -19,6 +19,7 @@ Organizations below are **officially** using Argo Rollouts. Please send a PR wit 1. [New Relic](https://newrelic.com/) 1. [Nitro](https://www.gonitro.com) 1. [Nozzle](https://nozzle.io) +1. [PagerDuty](https://www.pagerduty.com/) 1. [PayPal](https://www.paypal.com/) 1. [PayPay](https://paypay.ne.jp/) 1. [Quipper](https://www.quipper.com/) diff --git a/analysis/analysis_test.go b/analysis/analysis_test.go index d4763f3e4b..30e3b7adb9 100644 --- a/analysis/analysis_test.go +++ b/analysis/analysis_test.go @@ -1500,7 +1500,7 @@ func TestAssessRunStatusErrorMessageAnalysisPhaseFail(t *testing.T) { func TestAssessRunStatusErrorMessageAnalysisPhaseFailInDryRunMode(t *testing.T) { status, message, dryRunSummary := StartAssessRunStatusErrorMessageAnalysisPhaseFail(t, true) - assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status) + assert.Equal(t, v1alpha1.AnalysisPhaseRunning, status) assert.Equal(t, "", message) expectedDryRunSummary := v1alpha1.RunSummary{ Count: 2, @@ -1545,7 +1545,7 @@ func TestAssessRunStatusErrorMessageFromProvider(t *testing.T) { func TestAssessRunStatusErrorMessageFromProviderInDryRunMode(t *testing.T) { providerMessage := "Provider Error" status, message, dryRunSummary := StartAssessRunStatusErrorMessageFromProvider(t, providerMessage, true) - assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status) + assert.Equal(t, v1alpha1.AnalysisPhaseRunning, status) assert.Equal(t, "", message) expectedDryRunSummary := v1alpha1.RunSummary{ Count: 2, @@ -1587,7 +1587,7 @@ func TestAssessRunStatusMultipleFailures(t *testing.T) { func TestAssessRunStatusMultipleFailuresInDryRunMode(t *testing.T) { status, message, dryRunSummary := StartAssessRunStatusMultipleFailures(t, true) - assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, status) + assert.Equal(t, v1alpha1.AnalysisPhaseRunning, status) assert.Equal(t, "", message) expectedDryRunSummary := v1alpha1.RunSummary{ Count: 2, @@ -1623,7 +1623,7 @@ func TestAssessRunStatusWorstMessageInReconcileAnalysisRun(t *testing.T) { func TestAssessRunStatusWorstMessageInReconcileAnalysisRunInDryRunMode(t *testing.T) { newRun := StartAssessRunStatusWorstMessageInReconcileAnalysisRun(t, true) - assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, newRun.Status.Phase) + assert.Equal(t, v1alpha1.AnalysisPhaseRunning, newRun.Status.Phase) assert.Equal(t, "", newRun.Status.Message) expectedDryRunSummary := v1alpha1.RunSummary{ Count: 2, diff --git a/analysis/controller.go b/analysis/controller.go index 2be67de829..285cd1ff13 100644 --- a/analysis/controller.go +++ b/analysis/controller.go @@ -3,6 +3,8 @@ package analysis import ( "time" + unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" + log "github.com/sirupsen/logrus" batchv1 "k8s.io/api/batch/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -117,7 +119,14 @@ func NewController(cfg ControllerConfig) *Controller { UpdateFunc: func(old, new interface{}) { controller.enqueueAnalysis(new) }, - DeleteFunc: controller.enqueueAnalysis, + DeleteFunc: func(obj interface{}) { + controller.enqueueAnalysis(obj) + if ar := unstructuredutil.ObjectToAnalysisRun(obj); ar != nil { + logCtx := logutil.WithAnalysisRun(ar) + logCtx.Info("analysis run deleted") + controller.metricsServer.Remove(ar.Namespace, ar.Name, logutil.AnalysisRunKey) + } + }, }) return controller } diff --git a/controller/metrics/analysis_test.go b/controller/metrics/analysis_test.go index 992be05c9f..61ca87a80f 100644 --- a/controller/metrics/analysis_test.go +++ b/controller/metrics/analysis_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/ghodss/yaml" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -162,7 +164,7 @@ func testAnalysisRunDescribe(t *testing.T, fakeAnalysisRun string, expectedRespo registry.MustRegister(NewAnalysisRunCollector(serverCfg.AnalysisRunLister, serverCfg.AnalysisTemplateLister, serverCfg.ClusterAnalysisTemplateLister)) mux := http.NewServeMux() mux.Handle(MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) - testHttpResponse(t, mux, expectedResponse) + testHttpResponse(t, mux, expectedResponse, assert.Contains) } func TestIncAnalysisRunReconcile(t *testing.T) { @@ -184,7 +186,7 @@ analysis_run_reconcile_count{name="ar-test",namespace="ar-namespace"} 1` }, } metricsServ.IncAnalysisRunReconcile(ar, time.Millisecond) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } func TestAnalysisTemplateDescribe(t *testing.T) { @@ -206,5 +208,5 @@ analysis_template_metric_info{metric="web-metric-2",name="http-benchmark-test",n registry.MustRegister(NewAnalysisRunCollector(serverCfg.AnalysisRunLister, serverCfg.AnalysisTemplateLister, serverCfg.ClusterAnalysisTemplateLister)) mux := http.NewServeMux() mux.Handle(MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) - testHttpResponse(t, mux, expectedResponse) + testHttpResponse(t, mux, expectedResponse, assert.Contains) } diff --git a/controller/metrics/client_test.go b/controller/metrics/client_test.go index 9aedd570d1..0ebf470a68 100644 --- a/controller/metrics/client_test.go +++ b/controller/metrics/client_test.go @@ -3,6 +3,8 @@ package metrics import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/argoproj/pkg/kubeclientmetrics" ) @@ -24,5 +26,5 @@ func TestIncKubernetesRequest(t *testing.T) { Verb: kubeclientmetrics.Unknown, StatusCode: 200, }) - testHttpResponse(t, metricsServ.Handler, expectedKubernetesRequest) + testHttpResponse(t, metricsServ.Handler, expectedKubernetesRequest, assert.Contains) } diff --git a/controller/metrics/experiment_test.go b/controller/metrics/experiment_test.go index fa4482bf87..da050e9377 100644 --- a/controller/metrics/experiment_test.go +++ b/controller/metrics/experiment_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/ghodss/yaml" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -133,7 +135,7 @@ func testExperimentDescribe(t *testing.T, fakeExperiment string, expectedRespons registry.MustRegister(NewExperimentCollector(config.ExperimentLister)) mux := http.NewServeMux() mux.Handle(MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) - testHttpResponse(t, mux, expectedResponse) + testHttpResponse(t, mux, expectedResponse, assert.Contains) } func TestIncExperimentReconcile(t *testing.T) { @@ -156,5 +158,5 @@ experiment_reconcile_count{name="ex-test",namespace="ex-namespace"} 1` }, } metricsServ.IncExperimentReconcile(ex, time.Millisecond) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } diff --git a/controller/metrics/metrics.go b/controller/metrics/metrics.go index b5940bc61f..3c3bbfe246 100644 --- a/controller/metrics/metrics.go +++ b/controller/metrics/metrics.go @@ -4,6 +4,8 @@ import ( "net/http" "time" + "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" registry "k8s.io/component-base/metrics/legacyregistry" @@ -136,6 +138,51 @@ func (m *MetricsServer) IncError(namespace, name string, kind string) { } } +// Remove removes the metrics server from the registry +func (m *MetricsServer) Remove(namespace string, name string, kind string) { + go func(namespace string, name string, kind string) { + // wait for the metrics to be collected, prometheus scrape interval is 60 seconds by default + time.Sleep(defaults.GetMetricCleanupDelaySeconds()) + switch kind { + case log.RolloutKey: + m.reconcileRolloutHistogram.Delete(map[string]string{"namespace": namespace, "name": name}) + m.errorRolloutCounter.Delete(map[string]string{"namespace": namespace, "name": name}) + + m.successNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.errorNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.sendNotificationRunHistogram.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + + MetricRolloutReconcile.Delete(map[string]string{"namespace": namespace, "name": name}) + + MetricRolloutReconcileError.Delete(map[string]string{"namespace": namespace, "name": name}) + + MetricRolloutEventsTotal.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + case log.AnalysisRunKey: + m.reconcileAnalysisRunHistogram.Delete(map[string]string{"namespace": namespace, "name": name}) + m.errorAnalysisRunCounter.Delete(map[string]string{"namespace": namespace, "name": name}) + + m.successNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.errorNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.sendNotificationRunHistogram.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + + MetricAnalysisRunReconcile.Delete(map[string]string{"namespace": namespace, "name": name}) + MetricAnalysisRunReconcileError.Delete(map[string]string{"namespace": namespace, "name": name}) + + case log.ExperimentKey: + m.reconcileExperimentHistogram.Delete(map[string]string{"namespace": namespace, "name": name}) + m.errorExperimentCounter.Delete(map[string]string{"namespace": namespace, "name": name}) + + m.successNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.errorNotificationCounter.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + m.sendNotificationRunHistogram.DeletePartialMatch(map[string]string{"namespace": namespace, "name": name}) + + MetricExperimentReconcile.Delete(map[string]string{"namespace": namespace, "name": name}) + MetricExperimentReconcileError.Delete(map[string]string{"namespace": namespace, "name": name}) + } + }(namespace, name, kind) + +} + func boolFloat64(b bool) float64 { if b { return 1 diff --git a/controller/metrics/metrics_test.go b/controller/metrics/metrics_test.go index 098d576702..22d671cee1 100644 --- a/controller/metrics/metrics_test.go +++ b/controller/metrics/metrics_test.go @@ -7,7 +7,9 @@ import ( "net/http/httptest" "strings" "testing" + "time" + "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/cache" @@ -52,7 +54,7 @@ func newFakeServerConfig(objs ...runtime.Object) ServerConfig { } } -func testHttpResponse(t *testing.T, handler http.Handler, expectedResponse string) { +func testHttpResponse(t *testing.T, handler http.Handler, expectedResponse string, testFunc func(t assert.TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) bool) { t.Helper() req, err := http.NewRequest("GET", "/metrics", nil) assert.NoError(t, err) @@ -62,7 +64,7 @@ func testHttpResponse(t *testing.T, handler http.Handler, expectedResponse strin body := rr.Body.String() log.Println(body) for _, line := range strings.Split(expectedResponse, "\n") { - assert.Contains(t, body, line) + testFunc(t, body, line) } } @@ -77,6 +79,7 @@ func TestIncError(t *testing.T) { analysis_run_reconcile_error{name="name",namespace="ns"} 1 # HELP experiment_reconcile_error Error occurring during the experiment # TYPE experiment_reconcile_error counter +experiment_reconcile_error{name="name",namespace="ns"} 1 # HELP rollout_reconcile_error Error occurring during the rollout # TYPE rollout_reconcile_error counter rollout_reconcile_error{name="name",namespace="ns"} 1` @@ -86,19 +89,42 @@ rollout_reconcile_error{name="name",namespace="ns"} 1` metricsServ.IncError("ns", "name", logutil.AnalysisRunKey) metricsServ.IncError("ns", "name", logutil.ExperimentKey) metricsServ.IncError("ns", "name", logutil.RolloutKey) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } func TestVersionInfo(t *testing.T) { expectedResponse := `# HELP argo_rollouts_controller_info Running Argo-rollouts version # TYPE argo_rollouts_controller_info gauge` metricsServ := NewMetricsServer(newFakeServerConfig(), true) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } func TestSecondaryMetricsServer(t *testing.T) { expectedResponse := `` metricsServ := NewMetricsServer(newFakeServerConfig(), false) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) +} + +func TestRemove(t *testing.T) { + defaults.SetMetricCleanupDelaySeconds(1) + + expectedResponse := `analysis_run_reconcile_error{name="name1",namespace="ns"} 1 +experiment_reconcile_error{name="name1",namespace="ns"} 1 +rollout_reconcile_error{name="name1",namespace="ns"} 1` + + metricsServ := NewMetricsServer(newFakeServerConfig(), true) + + metricsServ.IncError("ns", "name1", logutil.RolloutKey) + metricsServ.IncError("ns", "name1", logutil.AnalysisRunKey) + metricsServ.IncError("ns", "name1", logutil.ExperimentKey) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) + + metricsServ.Remove("ns", "name1", logutil.AnalysisRunKey) + metricsServ.Remove("ns", "name1", logutil.ExperimentKey) + metricsServ.Remove("ns", "name1", logutil.RolloutKey) + + //Sleep for 2x the cleanup delay to allow metrics to be removed + time.Sleep(defaults.GetMetricCleanupDelaySeconds() * 2) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.NotContains) } diff --git a/controller/metrics/rollout_test.go b/controller/metrics/rollout_test.go index 1ff2495d14..b10af8b184 100644 --- a/controller/metrics/rollout_test.go +++ b/controller/metrics/rollout_test.go @@ -163,7 +163,7 @@ func testRolloutDescribe(t *testing.T, fakeRollout string, cond *v1alpha1.Rollou registry.MustRegister(NewRolloutCollector(config.RolloutLister)) mux := http.NewServeMux() mux.Handle(MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) - testHttpResponse(t, mux, expectedResponse) + testHttpResponse(t, mux, expectedResponse, assert.Contains) } func TestIncRolloutReconcile(t *testing.T) { @@ -188,7 +188,7 @@ rollout_reconcile_count{name="ro-test",namespace="ro-namespace"} 1 }, } metricsServ.IncRolloutReconcile(ro, time.Millisecond) - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } func TestGetStrategyAndTrafficRouter(t *testing.T) { @@ -310,5 +310,5 @@ rollout_events_total{name="ro-test-2",namespace="ro-namespace",reason="BazEvent" MetricRolloutEventsTotal.WithLabelValues("ro-namespace", "ro-test-1", corev1.EventTypeNormal, "BarEvent").Inc() MetricRolloutEventsTotal.WithLabelValues("ro-namespace", "ro-test-2", corev1.EventTypeWarning, "BazEvent").Inc() MetricRolloutEventsTotal.WithLabelValues("ro-namespace", "ro-test-2", corev1.EventTypeWarning, "BazEvent").Inc() - testHttpResponse(t, metricsServ.Handler, expectedResponse) + testHttpResponse(t, metricsServ.Handler, expectedResponse, assert.Contains) } diff --git a/docs/analysis/cloudwatch.md b/docs/analysis/cloudwatch.md index 145fad6f8b..7bccbc2072 100644 --- a/docs/analysis/cloudwatch.md +++ b/docs/analysis/cloudwatch.md @@ -11,7 +11,7 @@ You can use CloudWatch Metrics if you have used to EKS or not. This analysis is ### EKS -If you create new cluster on EKS, you can attach [cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) or attach [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). +If you create new cluster on EKS, you can attach [cluster IAM role](https://docs.aws.amazon.com/eks/latest/userguide/service_IAM_role.html) or attach [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). If you have already cluster on EKS, you can attach [IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). ### not EKS diff --git a/docs/analysis/influxdb.md b/docs/analysis/influxdb.md new file mode 100644 index 0000000000..f4bd95345e --- /dev/null +++ b/docs/analysis/influxdb.md @@ -0,0 +1,41 @@ +# InfluxDB Metrics + +An [InfluxDB](https://www.influxdata.com/) query using [Flux](https://docs.influxdata.com/influxdb/cloud/query-data/get-started/query-influxdb/) can be used to obtain measurements for analysis. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: AnalysisTemplate +metadata: + name: error-rate +spec: + args: + - name: application-name + metrics: + - name: error-rate + # NOTE: To be consistent with the prometheus metrics provider InfluxDB query results are returned as an array. + # In the example we're looking at index 0 of the returned array to obtain the value we're using for the success condition + successCondition: result[0] <= 0.01 + provider: + influxdb: + profile: my-influxdb-secret # optional, defaults to 'influxdb' + query: | + from(bucket: "app_istio") + |> range(start: -15m) + |> filter(fn: (r) => r["destination_workload"] == "{{ args.application-name }}") + |> filter(fn: (r) => r["_measurement"] == "istio:istio_requests_errors_percentage:rate1m:5xx") + +``` + +An InfluxDB access profile can be configured using a Kubernetes secret in the `argo-rollouts` namespace. Alternate accounts can be used by creating more secrets of the same format and specifying which secret to use in the metric provider configuration using the `profile` field. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: influxdb +type: Opaque +data: + address: + authToken: + org: +``` diff --git a/docs/analysis/kayenta.md b/docs/analysis/kayenta.md index 907ed9196d..06cc91b02a 100644 --- a/docs/analysis/kayenta.md +++ b/docs/analysis/kayenta.md @@ -1,6 +1,6 @@ ## Kayenta (e.g. Mann-Whitney Analysis) -Analysis can also be done as part of an [Experiment](../features/experiment.md). +Analysis can also be done as part of an [Experiment](../features/experiment.md). This example starts both a canary and baseline ReplicaSet. The ReplicaSets run for 1 hour, then scale down to zero. Call out to Kayenta to perform Mann-Whintney analysis against the two pods. Demonstrates ability to start a @@ -23,7 +23,7 @@ This example demonstrates: app: guestbook spec: strategy: - canary: + canary: steps: - experiment: duration: 1h @@ -45,7 +45,7 @@ This example demonstrates: === "AnalysisTemplate" - ```yaml + ```yaml apiVersion: argoproj.io/v1alpha1 kind: AnalysisTemplate metadata: diff --git a/docs/analysis/newrelic.md b/docs/analysis/newrelic.md index de1153505d..b71b661f84 100644 --- a/docs/analysis/newrelic.md +++ b/docs/analysis/newrelic.md @@ -3,7 +3,7 @@ !!! important Available since v0.10.0 -A [New Relic](https://newrelic.com/) query using [NRQL](https://docs.newrelic.com/docs/query-your-data/nrql-new-relic-query-language/get-started/introduction-nrql-new-relics-query-language) can be used to obtain measurements for analysis. +A [New Relic](https://newrelic.com/) query using [NRQL](https://docs.newrelic.com/docs/query-your-data/nrql-new-relic-query-language/get-started/introduction-nrql-new-relics-query-language) can be used to obtain measurements for analysis. ```yaml apiVersion: argoproj.io/v1alpha1 diff --git a/docs/analysis/prometheus.md b/docs/analysis/prometheus.md index 56736f0257..a698626d10 100644 --- a/docs/analysis/prometheus.md +++ b/docs/analysis/prometheus.md @@ -23,7 +23,7 @@ spec: query: | sum(irate( istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}",response_code!~"5.*"}[5m] - )) / + )) / sum(irate( istio_requests_total{reporter="source",destination_service=~"{{args.service-name}}"}[5m] )) @@ -36,5 +36,5 @@ See the [Analysis Overview page](../../features/analysis) for more details on th # Additional Metadata -Any additional metadata from the Prometheus controller, like the resolved queries after substituting the template's -arguments, etc. will appear under the `Metadata` map in the `MetricsResult` object of `AnalysisRun`. \ No newline at end of file +Any additional metadata from the Prometheus controller, like the resolved queries after substituting the template's +arguments, etc. will appear under the `Metadata` map in the `MetricsResult` object of `AnalysisRun`. diff --git a/docs/analysis/wavefront.md b/docs/analysis/wavefront.md index 7eb58863e2..f7c8f57d91 100644 --- a/docs/analysis/wavefront.md +++ b/docs/analysis/wavefront.md @@ -39,4 +39,3 @@ data: example1.wavefront.com: example2.wavefront.com: ``` - diff --git a/docs/analysis/web.md b/docs/analysis/web.md index e3e133cec0..285403ffe7 100644 --- a/docs/analysis/web.md +++ b/docs/analysis/web.md @@ -17,7 +17,7 @@ of the as the result variable. headers: - key: Authorization value: "Bearer {{ args.api-token }}" - jsonPath: "{$.data.ok}" + jsonPath: "{$.data.ok}" ``` In the following example, given the payload, the measurement will be Successful if the `data.ok` field was `true`, and the `data.successPercent` @@ -42,7 +42,7 @@ was greater than `0.90` headers: - key: Authorization value: "Bearer {{ args.api-token }}" - jsonPath: "{$.data}" + jsonPath: "{$.data}" ``` NOTE: if the result is a string, two convenience functions `asInt` and `asFloat` are provided @@ -67,7 +67,7 @@ It is possible to use a POST or PUT requests, by specifying the `method` and `bo - key: Content-Type # if body is a json, it is recommended to set the Content-Type value: "application/json" body: "{\"key\": \"string value\"}" - jsonPath: "{$.data.ok}" + jsonPath: "{$.data.ok}" ``` !!! tip In order to send in JSON, you have to encode it yourself, and send the correct Content-Type as well. diff --git a/docs/features/kustomize/rollout_cr_schema.json b/docs/features/kustomize/rollout_cr_schema.json index 7b57b232e1..4ec83fccff 100644 --- a/docs/features/kustomize/rollout_cr_schema.json +++ b/docs/features/kustomize/rollout_cr_schema.json @@ -1,6 +1,16647 @@ { "definitions": { - "v1alpha1.Rollout": { + "io.argoproj.v1alpha1.AnalysisRun": { + "properties": { + "spec": { + "properties": { + "args": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "fieldRef": { + "properties": { + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "dryRun": { + "items": { + "properties": { + "metricName": { + "type": "string" + } + }, + "required": [ + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "measurementRetention": { + "items": { + "properties": { + "limit": { + "format": "int32", + "type": "integer" + }, + "metricName": { + "type": "string" + } + }, + "required": [ + "limit", + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "metrics": { + "items": { + "properties": { + "consecutiveErrorLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "failureCondition": { + "type": "string" + }, + "failureLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "inconclusiveLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "initialDelay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "name": { + "type": "string" + }, + "provider": { + "properties": { + "cloudWatch": { + "properties": { + "interval": { + "type": "string" + }, + "metricDataQueries": { + "items": { + "properties": { + "expression": { + "type": "string" + }, + "id": { + "type": "string" + }, + "label": { + "type": "string" + }, + "metricStat": { + "properties": { + "metric": { + "properties": { + "dimensions": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "metricName": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "stat": { + "type": "string" + }, + "unit": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "returnData": { + "type": "boolean" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "metricDataQueries" + ], + "type": "object" + }, + "datadog": { + "properties": { + "interval": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "graphite": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "influxdb": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "job": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "backoffLimit": { + "format": "int32", + "type": "integer" + }, + "completionMode": { + "type": "string" + }, + "completions": { + "format": "int32", + "type": "integer" + }, + "manualSelector": { + "type": "boolean" + }, + "parallelism": { + "format": "int32", + "type": "integer" + }, + "selector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "suspend": { + "type": "boolean" + }, + "template": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "affinity": { + "properties": { + "nodeAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "preference": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "preference", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "properties": { + "nodeSelectorTerms": { + "items": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object" + } + }, + "type": "object" + }, + "podAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "podAntiAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + }, + "containers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "dnsConfig": { + "properties": { + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "searches": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dnsPolicy": { + "type": "string" + }, + "enableServiceLinks": { + "type": "boolean" + }, + "ephemeralContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "targetContainerName": { + "type": "string" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "hostAliases": { + "items": { + "properties": { + "hostnames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "hostIPC": { + "type": "boolean" + }, + "hostNetwork": { + "type": "boolean" + }, + "hostPID": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "imagePullSecrets": { + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "initContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "nodeName": { + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "os": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "overhead": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "preemptionPolicy": { + "type": "string" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessGates": { + "items": { + "properties": { + "conditionType": { + "type": "string" + } + }, + "required": [ + "conditionType" + ], + "type": "object" + }, + "type": "array" + }, + "restartPolicy": { + "type": "string" + }, + "runtimeClassName": { + "type": "string" + }, + "schedulerName": { + "type": "string" + }, + "securityContext": { + "properties": { + "fsGroup": { + "format": "int64", + "type": "integer" + }, + "fsGroupChangePolicy": { + "type": "string" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "supplementalGroups": { + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array" + }, + "sysctls": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceAccount": { + "type": "string" + }, + "serviceAccountName": { + "type": "string" + }, + "setHostnameAsFQDN": { + "type": "boolean" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "subdomain": { + "type": "string" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "tolerations": { + "items": { + "properties": { + "effect": { + "type": "string" + }, + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "tolerationSeconds": { + "format": "int64", + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "topologySpreadConstraints": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "maxSkew": { + "format": "int32", + "type": "integer" + }, + "minDomains": { + "format": "int32", + "type": "integer" + }, + "topologyKey": { + "type": "string" + }, + "whenUnsatisfiable": { + "type": "string" + } + }, + "required": [ + "maxSkew", + "topologyKey", + "whenUnsatisfiable" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "topologyKey", + "whenUnsatisfiable" + ], + "x-kubernetes-list-type": "map" + }, + "volumes": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "containers" + ], + "type": "object" + } + }, + "type": "object" + }, + "ttlSecondsAfterFinished": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "template" + ], + "type": "object" + } + }, + "required": [ + "spec" + ], + "type": "object" + }, + "kayenta": { + "properties": { + "address": { + "type": "string" + }, + "application": { + "type": "string" + }, + "canaryConfigName": { + "type": "string" + }, + "configurationAccountName": { + "type": "string" + }, + "metricsAccountName": { + "type": "string" + }, + "scopes": { + "items": { + "properties": { + "controlScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "experimentScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "name": { + "type": "string" + } + }, + "required": [ + "controlScope", + "experimentScope", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "storageAccountName": { + "type": "string" + }, + "threshold": { + "properties": { + "marginal": { + "format": "int64", + "type": "integer" + }, + "pass": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "marginal", + "pass" + ], + "type": "object" + } + }, + "required": [ + "address", + "application", + "canaryConfigName", + "configurationAccountName", + "metricsAccountName", + "scopes", + "storageAccountName", + "threshold" + ], + "type": "object" + }, + "newRelic": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "prometheus": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "wavefront": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "web": { + "properties": { + "body": { + "type": "string" + }, + "headers": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "insecure": { + "type": "boolean" + }, + "jsonPath": { + "type": "string" + }, + "method": { + "type": "string" + }, + "timeoutSeconds": { + "format": "int64", + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + }, + "type": "object" + }, + "successCondition": { + "type": "string" + } + }, + "required": [ + "name", + "provider" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "required": [ + "metrics" + ], + "type": "object" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "argoproj.io", + "kind": "AnalysisRun", + "version": "v1alpha1" + } + ] + }, + "io.argoproj.v1alpha1.AnalysisTemplate": { + "properties": { + "spec": { + "properties": { + "args": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "fieldRef": { + "properties": { + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "dryRun": { + "items": { + "properties": { + "metricName": { + "type": "string" + } + }, + "required": [ + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "measurementRetention": { + "items": { + "properties": { + "limit": { + "format": "int32", + "type": "integer" + }, + "metricName": { + "type": "string" + } + }, + "required": [ + "limit", + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "metrics": { + "items": { + "properties": { + "consecutiveErrorLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "failureCondition": { + "type": "string" + }, + "failureLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "inconclusiveLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "initialDelay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "name": { + "type": "string" + }, + "provider": { + "properties": { + "cloudWatch": { + "properties": { + "interval": { + "type": "string" + }, + "metricDataQueries": { + "items": { + "properties": { + "expression": { + "type": "string" + }, + "id": { + "type": "string" + }, + "label": { + "type": "string" + }, + "metricStat": { + "properties": { + "metric": { + "properties": { + "dimensions": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "metricName": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "stat": { + "type": "string" + }, + "unit": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "returnData": { + "type": "boolean" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "metricDataQueries" + ], + "type": "object" + }, + "datadog": { + "properties": { + "interval": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "graphite": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "influxdb": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "job": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "backoffLimit": { + "format": "int32", + "type": "integer" + }, + "completionMode": { + "type": "string" + }, + "completions": { + "format": "int32", + "type": "integer" + }, + "manualSelector": { + "type": "boolean" + }, + "parallelism": { + "format": "int32", + "type": "integer" + }, + "selector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "suspend": { + "type": "boolean" + }, + "template": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "affinity": { + "properties": { + "nodeAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "preference": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "preference", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "properties": { + "nodeSelectorTerms": { + "items": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object" + } + }, + "type": "object" + }, + "podAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "podAntiAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + }, + "containers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "dnsConfig": { + "properties": { + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "searches": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dnsPolicy": { + "type": "string" + }, + "enableServiceLinks": { + "type": "boolean" + }, + "ephemeralContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "targetContainerName": { + "type": "string" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "hostAliases": { + "items": { + "properties": { + "hostnames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "hostIPC": { + "type": "boolean" + }, + "hostNetwork": { + "type": "boolean" + }, + "hostPID": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "imagePullSecrets": { + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "initContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "nodeName": { + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "os": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "overhead": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "preemptionPolicy": { + "type": "string" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessGates": { + "items": { + "properties": { + "conditionType": { + "type": "string" + } + }, + "required": [ + "conditionType" + ], + "type": "object" + }, + "type": "array" + }, + "restartPolicy": { + "type": "string" + }, + "runtimeClassName": { + "type": "string" + }, + "schedulerName": { + "type": "string" + }, + "securityContext": { + "properties": { + "fsGroup": { + "format": "int64", + "type": "integer" + }, + "fsGroupChangePolicy": { + "type": "string" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "supplementalGroups": { + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array" + }, + "sysctls": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceAccount": { + "type": "string" + }, + "serviceAccountName": { + "type": "string" + }, + "setHostnameAsFQDN": { + "type": "boolean" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "subdomain": { + "type": "string" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "tolerations": { + "items": { + "properties": { + "effect": { + "type": "string" + }, + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "tolerationSeconds": { + "format": "int64", + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "topologySpreadConstraints": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "maxSkew": { + "format": "int32", + "type": "integer" + }, + "minDomains": { + "format": "int32", + "type": "integer" + }, + "topologyKey": { + "type": "string" + }, + "whenUnsatisfiable": { + "type": "string" + } + }, + "required": [ + "maxSkew", + "topologyKey", + "whenUnsatisfiable" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "topologyKey", + "whenUnsatisfiable" + ], + "x-kubernetes-list-type": "map" + }, + "volumes": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "containers" + ], + "type": "object" + } + }, + "type": "object" + }, + "ttlSecondsAfterFinished": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "template" + ], + "type": "object" + } + }, + "required": [ + "spec" + ], + "type": "object" + }, + "kayenta": { + "properties": { + "address": { + "type": "string" + }, + "application": { + "type": "string" + }, + "canaryConfigName": { + "type": "string" + }, + "configurationAccountName": { + "type": "string" + }, + "metricsAccountName": { + "type": "string" + }, + "scopes": { + "items": { + "properties": { + "controlScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "experimentScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "name": { + "type": "string" + } + }, + "required": [ + "controlScope", + "experimentScope", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "storageAccountName": { + "type": "string" + }, + "threshold": { + "properties": { + "marginal": { + "format": "int64", + "type": "integer" + }, + "pass": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "marginal", + "pass" + ], + "type": "object" + } + }, + "required": [ + "address", + "application", + "canaryConfigName", + "configurationAccountName", + "metricsAccountName", + "scopes", + "storageAccountName", + "threshold" + ], + "type": "object" + }, + "newRelic": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "prometheus": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "wavefront": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "web": { + "properties": { + "body": { + "type": "string" + }, + "headers": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "insecure": { + "type": "boolean" + }, + "jsonPath": { + "type": "string" + }, + "method": { + "type": "string" + }, + "timeoutSeconds": { + "format": "int64", + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + }, + "type": "object" + }, + "successCondition": { + "type": "string" + } + }, + "required": [ + "name", + "provider" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "required": [ + "metrics" + ], + "type": "object" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "argoproj.io", + "kind": "AnalysisTemplate", + "version": "v1alpha1" + } + ] + }, + "io.argoproj.v1alpha1.ClusterAnalysisTemplate": { + "properties": { + "spec": { + "properties": { + "args": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "fieldRef": { + "properties": { + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "dryRun": { + "items": { + "properties": { + "metricName": { + "type": "string" + } + }, + "required": [ + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "measurementRetention": { + "items": { + "properties": { + "limit": { + "format": "int32", + "type": "integer" + }, + "metricName": { + "type": "string" + } + }, + "required": [ + "limit", + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "metrics": { + "items": { + "properties": { + "consecutiveErrorLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "count": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "failureCondition": { + "type": "string" + }, + "failureLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "inconclusiveLimit": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "initialDelay": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "name": { + "type": "string" + }, + "provider": { + "properties": { + "cloudWatch": { + "properties": { + "interval": { + "type": "string" + }, + "metricDataQueries": { + "items": { + "properties": { + "expression": { + "type": "string" + }, + "id": { + "type": "string" + }, + "label": { + "type": "string" + }, + "metricStat": { + "properties": { + "metric": { + "properties": { + "dimensions": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "metricName": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "stat": { + "type": "string" + }, + "unit": { + "type": "string" + } + }, + "type": "object" + }, + "period": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "returnData": { + "type": "boolean" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "metricDataQueries" + ], + "type": "object" + }, + "datadog": { + "properties": { + "interval": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "graphite": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "influxdb": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "job": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "backoffLimit": { + "format": "int32", + "type": "integer" + }, + "completionMode": { + "type": "string" + }, + "completions": { + "format": "int32", + "type": "integer" + }, + "manualSelector": { + "type": "boolean" + }, + "parallelism": { + "format": "int32", + "type": "integer" + }, + "selector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "suspend": { + "type": "boolean" + }, + "template": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "affinity": { + "properties": { + "nodeAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "preference": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "preference", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "properties": { + "nodeSelectorTerms": { + "items": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object" + } + }, + "type": "object" + }, + "podAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "podAntiAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + }, + "containers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "dnsConfig": { + "properties": { + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "searches": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dnsPolicy": { + "type": "string" + }, + "enableServiceLinks": { + "type": "boolean" + }, + "ephemeralContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "targetContainerName": { + "type": "string" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "hostAliases": { + "items": { + "properties": { + "hostnames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "hostIPC": { + "type": "boolean" + }, + "hostNetwork": { + "type": "boolean" + }, + "hostPID": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "imagePullSecrets": { + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "initContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "nodeName": { + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "os": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "overhead": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "preemptionPolicy": { + "type": "string" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessGates": { + "items": { + "properties": { + "conditionType": { + "type": "string" + } + }, + "required": [ + "conditionType" + ], + "type": "object" + }, + "type": "array" + }, + "restartPolicy": { + "type": "string" + }, + "runtimeClassName": { + "type": "string" + }, + "schedulerName": { + "type": "string" + }, + "securityContext": { + "properties": { + "fsGroup": { + "format": "int64", + "type": "integer" + }, + "fsGroupChangePolicy": { + "type": "string" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "supplementalGroups": { + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array" + }, + "sysctls": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceAccount": { + "type": "string" + }, + "serviceAccountName": { + "type": "string" + }, + "setHostnameAsFQDN": { + "type": "boolean" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "subdomain": { + "type": "string" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "tolerations": { + "items": { + "properties": { + "effect": { + "type": "string" + }, + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "tolerationSeconds": { + "format": "int64", + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "topologySpreadConstraints": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "maxSkew": { + "format": "int32", + "type": "integer" + }, + "minDomains": { + "format": "int32", + "type": "integer" + }, + "topologyKey": { + "type": "string" + }, + "whenUnsatisfiable": { + "type": "string" + } + }, + "required": [ + "maxSkew", + "topologyKey", + "whenUnsatisfiable" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "topologyKey", + "whenUnsatisfiable" + ], + "x-kubernetes-list-type": "map" + }, + "volumes": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "containers" + ], + "type": "object" + } + }, + "type": "object" + }, + "ttlSecondsAfterFinished": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "template" + ], + "type": "object" + } + }, + "required": [ + "spec" + ], + "type": "object" + }, + "kayenta": { + "properties": { + "address": { + "type": "string" + }, + "application": { + "type": "string" + }, + "canaryConfigName": { + "type": "string" + }, + "configurationAccountName": { + "type": "string" + }, + "metricsAccountName": { + "type": "string" + }, + "scopes": { + "items": { + "properties": { + "controlScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "experimentScope": { + "properties": { + "end": { + "type": "string" + }, + "region": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "start": { + "type": "string" + }, + "step": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "end", + "region", + "scope", + "start", + "step" + ], + "type": "object" + }, + "name": { + "type": "string" + } + }, + "required": [ + "controlScope", + "experimentScope", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "storageAccountName": { + "type": "string" + }, + "threshold": { + "properties": { + "marginal": { + "format": "int64", + "type": "integer" + }, + "pass": { + "format": "int64", + "type": "integer" + } + }, + "required": [ + "marginal", + "pass" + ], + "type": "object" + } + }, + "required": [ + "address", + "application", + "canaryConfigName", + "configurationAccountName", + "metricsAccountName", + "scopes", + "storageAccountName", + "threshold" + ], + "type": "object" + }, + "newRelic": { + "properties": { + "profile": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "prometheus": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "wavefront": { + "properties": { + "address": { + "type": "string" + }, + "query": { + "type": "string" + } + }, + "type": "object" + }, + "web": { + "properties": { + "body": { + "type": "string" + }, + "headers": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "insecure": { + "type": "boolean" + }, + "jsonPath": { + "type": "string" + }, + "method": { + "type": "string" + }, + "timeoutSeconds": { + "format": "int64", + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "required": [ + "url" + ], + "type": "object" + } + }, + "type": "object" + }, + "successCondition": { + "type": "string" + } + }, + "required": [ + "name", + "provider" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "required": [ + "metrics" + ], + "type": "object" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "argoproj.io", + "kind": "ClusterAnalysisTemplate", + "version": "v1alpha1" + } + ] + }, + "io.argoproj.v1alpha1.Experiment": { + "properties": { + "spec": { + "properties": { + "analyses": { + "items": { + "properties": { + "args": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "fieldRef": { + "properties": { + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key", + "name" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "clusterScope": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "requiredForCompletion": { + "type": "boolean" + }, + "templateName": { + "type": "string" + } + }, + "required": [ + "name", + "templateName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + }, + "dryRun": { + "items": { + "properties": { + "metricName": { + "type": "string" + } + }, + "required": [ + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "measurementRetention": { + "items": { + "properties": { + "limit": { + "format": "int32", + "type": "integer" + }, + "metricName": { + "type": "string" + } + }, + "required": [ + "limit", + "metricName" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "metricName", + "x-kubernetes-patch-strategy": "merge" + }, + "templates": { + "items": { + "properties": { + "minReadySeconds": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "replicas": { + "format": "int32", + "type": "integer" + }, + "selector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "service": { + "type": "object" + }, + "template": { + "properties": { + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "spec": { + "properties": { + "activeDeadlineSeconds": { + "format": "int64", + "type": "integer" + }, + "affinity": { + "properties": { + "nodeAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "preference": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "preference", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "properties": { + "nodeSelectorTerms": { + "items": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchFields": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "nodeSelectorTerms" + ], + "type": "object" + } + }, + "type": "object" + }, + "podAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "podAntiAffinity": { + "properties": { + "preferredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "podAffinityTerm": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "weight": { + "format": "int32", + "type": "integer" + } + }, + "required": [ + "podAffinityTerm", + "weight" + ], + "type": "object" + }, + "type": "array" + }, + "requiredDuringSchedulingIgnoredDuringExecution": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaceSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": "array" + }, + "topologyKey": { + "type": "string" + } + }, + "required": [ + "topologyKey" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "automountServiceAccountToken": { + "type": "boolean" + }, + "containers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "dnsConfig": { + "properties": { + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "searches": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dnsPolicy": { + "type": "string" + }, + "enableServiceLinks": { + "type": "boolean" + }, + "ephemeralContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "targetContainerName": { + "type": "string" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "hostAliases": { + "items": { + "properties": { + "hostnames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "hostIPC": { + "type": "boolean" + }, + "hostNetwork": { + "type": "boolean" + }, + "hostPID": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "imagePullSecrets": { + "items": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "initContainers": { + "items": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "command": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "valueFrom": { + "properties": { + "configMapKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "fieldRef": { + "properties": { + "apiVersion": { + "type": "string" + }, + "fieldPath": { + "type": "string" + } + }, + "required": [ + "fieldPath" + ], + "type": "object" + }, + "resourceFieldRef": { + "properties": { + "containerName": { + "type": "string" + }, + "divisor": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "resource": { + "type": "string" + } + }, + "required": [ + "resource" + ], + "type": "object" + }, + "secretKeyRef": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "envFrom": { + "items": { + "properties": { + "configMapRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + }, + "prefix": { + "type": "string" + }, + "secretRef": { + "properties": { + "name": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "imagePullPolicy": { + "type": "string" + }, + "lifecycle": { + "properties": { + "postStart": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + }, + "preStop": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "ports": { + "items": { + "properties": { + "containerPort": { + "format": "int32", + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "format": "int32", + "type": "integer" + }, + "name": { + "type": "string" + }, + "protocol": { + "default": "TCP", + "type": "string" + } + }, + "required": [ + "containerPort" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "containerPort", + "protocol" + ], + "x-kubernetes-list-type": "map" + }, + "readinessProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "properties": { + "limits": { + "x-kubernetes-preserve-unknown-fields": true + }, + "requests": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "type": "object" + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "properties": { + "add": { + "items": { + "type": "string" + }, + "type": "array" + }, + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "procMount": { + "type": "string" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "startupProbe": { + "properties": { + "exec": { + "properties": { + "command": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "failureThreshold": { + "format": "int32", + "type": "integer" + }, + "grpc": { + "properties": { + "port": { + "format": "int32", + "type": "integer" + }, + "service": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "properties": { + "host": { + "type": "string" + }, + "httpHeaders": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + }, + "scheme": { + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "properties": { + "host": { + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "stdin": { + "type": "boolean" + }, + "stdinOnce": { + "type": "boolean" + }, + "terminationMessagePath": { + "type": "string" + }, + "terminationMessagePolicy": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "volumeDevices": { + "items": { + "properties": { + "devicePath": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "devicePath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "volumeMounts": { + "items": { + "properties": { + "mountPath": { + "type": "string" + }, + "mountPropagation": { + "type": "string" + }, + "name": { + "type": "string" + }, + "readOnly": { + "type": "boolean" + }, + "subPath": { + "type": "string" + }, + "subPathExpr": { + "type": "string" + } + }, + "required": [ + "mountPath", + "name" + ], + "type": "object" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + }, + "nodeName": { + "type": "string" + }, + "nodeSelector": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "os": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "overhead": { + "additionalProperties": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "pattern": "^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$", + "x-kubernetes-int-or-string": true + }, + "type": "object" + }, + "preemptionPolicy": { + "type": "string" + }, + "priority": { + "format": "int32", + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessGates": { + "items": { + "properties": { + "conditionType": { + "type": "string" + } + }, + "required": [ + "conditionType" + ], + "type": "object" + }, + "type": "array" + }, + "restartPolicy": { + "type": "string" + }, + "runtimeClassName": { + "type": "string" + }, + "schedulerName": { + "type": "string" + }, + "securityContext": { + "properties": { + "fsGroup": { + "format": "int64", + "type": "integer" + }, + "fsGroupChangePolicy": { + "type": "string" + }, + "runAsGroup": { + "format": "int64", + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "format": "int64", + "type": "integer" + }, + "seLinuxOptions": { + "properties": { + "level": { + "type": "string" + }, + "role": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "seccompProfile": { + "properties": { + "localhostProfile": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "supplementalGroups": { + "items": { + "format": "int64", + "type": "integer" + }, + "type": "array" + }, + "sysctls": { + "items": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array" + }, + "windowsOptions": { + "properties": { + "gmsaCredentialSpec": { + "type": "string" + }, + "gmsaCredentialSpecName": { + "type": "string" + }, + "hostProcess": { + "type": "boolean" + }, + "runAsUserName": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceAccount": { + "type": "string" + }, + "serviceAccountName": { + "type": "string" + }, + "setHostnameAsFQDN": { + "type": "boolean" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "subdomain": { + "type": "string" + }, + "terminationGracePeriodSeconds": { + "format": "int64", + "type": "integer" + }, + "tolerations": { + "items": { + "properties": { + "effect": { + "type": "string" + }, + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "tolerationSeconds": { + "format": "int64", + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "topologySpreadConstraints": { + "items": { + "properties": { + "labelSelector": { + "properties": { + "matchExpressions": { + "items": { + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string" + }, + "values": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "key", + "operator" + ], + "type": "object" + }, + "type": "array" + }, + "matchLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "maxSkew": { + "format": "int32", + "type": "integer" + }, + "minDomains": { + "format": "int32", + "type": "integer" + }, + "topologyKey": { + "type": "string" + }, + "whenUnsatisfiable": { + "type": "string" + } + }, + "required": [ + "maxSkew", + "topologyKey", + "whenUnsatisfiable" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "topologyKey", + "whenUnsatisfiable" + ], + "x-kubernetes-list-type": "map" + }, + "volumes": { + "x-kubernetes-preserve-unknown-fields": true + } + }, + "required": [ + "containers" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "name", + "selector", + "template" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge" + } + }, + "required": [ + "templates" + ], + "type": "object" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "argoproj.io", + "kind": "Experiment", + "version": "v1alpha1" + } + ] + }, + "io.argoproj.v1alpha1.Rollout": { "properties": { "spec": { "properties": { @@ -3070,6 +19711,10 @@ "format": "int32", "type": "integer" }, + "minDomains": { + "format": "int32", + "type": "integer" + }, "topologyKey": { "type": "string" }, diff --git a/docs/features/specification.md b/docs/features/specification.md index dbea102893..100ddbb430 100644 --- a/docs/features/specification.md +++ b/docs/features/specification.md @@ -268,19 +268,52 @@ spec: # Setting header based route will send all 100 traffic to the canary for the requests # O with a specified header, in this case request header "version":"2" # (supported only with trafficRouting, for Istio only at the moment) - - setHeaderRouting: + - setHeaderRoute: + # Name of the route that will be created by argo rollouts this must also be configured + # in spec.strategy.canary.trafficRouting.managedRoutes + name: "header-route-1" + # The matching rules for the header route, if this is missing it acts as a removal of the route. match: + # headerName The name of the header to apply the match rules to. - headerName: "version" + # headerValue must contain exactly one field of exact, regex, or prefix. Not all traffic routers support + # all types headerValue: + # Exact will only match if the header value is exactly the same exact: "2" - - # Sets header based route with specified header values using regex as a value - # Could be used 'headerValue' or 'headerRegex' one of that values - - setHeaderRouting: - match: - - headerName: "version" - headerValue: + # Will match the rule if the regular expression matches regex: "2.0.(.*)" + # prefix will be a prefix match of the header value + prefix: "2.0" + + # Sets up a mirror/shadow based route with the specified match rules + # The traffic will be mirrored at the configured percentage to the canary service + # during the rollout + # (supported only with trafficRouting, for Istio only at the moment) + - setMirrorRoute: + # Name of the route that will be created by argo rollouts this must also be configured + # in spec.strategy.canary.trafficRouting.managedRoutes + name: "header-route-1" + # The percentage of the matched traffic to mirror to the canary + percentage: 100 + # The matching rules for the header route, if this is missing it acts as a removal of the route. + # All conditions inside a single match block have AND semantics, while the list of match blocks have OR semantics. + # Each type within a match (method, path, headers) must have one and only one match type (exact, regex, prefix) + # Not all match types (exact, regex, prefix) will be supported by all traffic routers. + match: + - method: # What HTTP method to match + exact: "GET" + regex: "P.*" + prefix: "POST" + path: # What HTTP url paths to match. + exact: "/test" + regex: ""/test/.*" + prefix: ""/" + headers: + agent-1b: # What HTTP header name to use in the match. + exact: "firefox" + regex: "firefox2(.*)" + prefix: "firefox" # an inline analysis step - analysis: @@ -311,7 +344,14 @@ spec: # will achieve traffic split via a weighted replica counts between # the canary and stable ReplicaSet. trafficRouting: - + # This is a list of routes that Argo Rollouts has the rights to manage it is currently only required for + # setMirrorRoute and setHeaderRoute. The order of managedRoutes array also sets the precedence of the route + # in the traffic router. Argo Rollouts will place these routes in the order specified above any routes already + # defined in the used traffic router if something exists. The names here must match the names from the + # setHeaderRoute and setMirrorRoute steps. + managedRoutes: + - name: set-header + - name: mirror-route # Istio traffic routing configuration istio: # Either virtualService or virtualServices can be configured. diff --git a/docs/features/traffic-management/alb.md b/docs/features/traffic-management/alb.md index 7139c3db7a..8255b74852 100644 --- a/docs/features/traffic-management/alb.md +++ b/docs/features/traffic-management/alb.md @@ -212,7 +212,7 @@ controller to verify that TargetGroups are accurate before marking newly created preventing premature scale down of the older ReplicaSet. Pod readiness gate injection uses a mutating webhook which decides to inject readiness gates when a -pod is created based on the following conditions: +pod is created based on the following conditions: * There exists a service matching the pod labels in the same namespace * There exists at least one target group binding that refers to the matching service @@ -343,8 +343,8 @@ The Rollout status object holds the value of who is currently the stable ping or And this way allows the rollout to use pod readiness gate injection as the services are not changing their labels at the end of the rollout progress. -!!!important - +!!!important + Ping-Pong feature available since Argo Rollouts v1.2 ## Example @@ -368,7 +368,7 @@ spec: ports: - containerPort: 80 strategy: - canary: + canary: pingPong: #Indicates that the ping-pong services enabled pingService: ping-service pongService: pong-service @@ -401,7 +401,7 @@ spec: annotationPrefix: custom.alb.ingress.kubernetes.io ``` -### Custom kubernetes.io/ingress.class +### Custom Ingress Class By default, Argo Rollout will operate on Ingresses with the annotation: @@ -413,14 +413,22 @@ metadata: kubernetes.io/ingress.class: alb ``` -To configure the controller to operate on Ingresses with different `kubernetes.io/ingress.class` -values, the controller can specify a different value through the `--alb-ingress-classes` flag in +Or with the `ingressClassName`: +```yaml +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +spec: + ingressClassName: alb +``` + +To configure the controller to operate on Ingresses with a different class name, +you can specify a different value through the `--alb-ingress-classes` flag in the controller command line arguments. Note that the `--alb-ingress-classes` flag can be specified multiple times if the Argo Rollouts controller should operate on multiple values. This may be desired when a cluster has multiple -Ingress controllers that operate on different `kubernetes.io/ingress.class` values. +Ingress controllers that operate on different `kubernetes.io/ingress.class` or `spec.ingressClassName` values. If the controller needs to operate on any Ingress without the `kubernetes.io/ingress.class` -annotation, the flag can be specified with an empty string (e.g. `--alb-ingress-classes ''`). +annotation or `spec.ingressClassName`, the flag can be specified with an empty string (e.g. `--alb-ingress-classes ''`). diff --git a/docs/features/traffic-management/index.md b/docs/features/traffic-management/index.md index 78561992ca..8c2e97d152 100644 --- a/docs/features/traffic-management/index.md +++ b/docs/features/traffic-management/index.md @@ -49,14 +49,53 @@ Since the traffic is controlled independently by the Service Mesh resources, the [^1]: The Rollout has to assume that the application can handle 100% of traffic if it is fully scaled up. It should outsource to the HPA to detect if the Rollout needs to more replicas if 100% isn't enough. +## Traffic routing with managed routes and route precedence +##### Traffic router support: (Istio) + +When traffic routing is enabled, you have the ability to also let argo rollouts add and manage other routes besides just +controlling the traffic weight to the canary. Two such routing rules are header and mirror based routes. When using these +routes we also have to set a route precedence with the upstream traffic router. We do this using the `spec.strategy.canary.trafficRouting.managedRoutes` +field which is an array the order of the items in the array determine the precedence. This set of routes will also be placed +in the order specified on top of any other routes defined manually. + +#### WARNING: All routes listed in managed routes will be removed at the end of a rollout or on an abort. Do not put any manually created routes in the list. + + +Here is an example: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +spec: + ... + strategy: + canary: + ... + trafficRouting: + managedRoutes: + - name: priority-route-1 + - name: priority-route-2 + - name: priority-route-3 +``` + + ## Traffic routing based on a header values for Canary +##### Traffic router support: (Istio) + +Argo Rollouts has ability to send all traffic to the canary-service based on a http request header value. +The step for the header based traffic routing is `setHeaderRoute` and has a list of matchers for the header. -Argo Rollouts has ability to send all traffic to the canary-service based on a http request header value. Right now it's implemented for the Istio only. -The step for the header based traffic routing `setHeaderRouting` has a list of matchers for the header. -Should be specified the `headerName` - name of the header and a value. -The value could be one of 3 `exact` - specify the exact header value, `regex` - value in a regex format, `prefix` - the prefix of the value could be provided. +`name` - name of the header route. -To disable header based traffic routing just need to specify empty `setHeaderRouting`. +`match` - header matching rules is an array of `headerName, headerValue` pairs. + +`headerName` - name of the header to match. + +`headerValue`- contains exactly one of `exact` - specify the exact header value, +`regex` - value in a regex format, `prefix` - the prefix of the value could be provided. Not all traffic routers will support +all match types. + +To disable header based traffic routing just need to specify empty `setHeaderRoute` with only the name of the route. Example: @@ -70,12 +109,15 @@ spec: canaryService: canary-service stableService: stable-service trafficRouting: + managedRoutes: + - name: set-header-1 istio: virtualService: name: rollouts-demo-vsvc steps: - setWeight: 20 - - setHeaderRouting: # enable header based traffic routing where + - setHeaderRoute: # enable header based traffic routing where + name: "set-header-1" match: - headerName: Custom-Header1 # Custom-Header1=Mozilla headerValue: @@ -87,5 +129,56 @@ spec: headerValue: regex: Mozilla(.*) - pause: {} - - setHeaderRouting: {} # disable header based traffic routing + - setHeaderRoute: + name: "set-header-1" # disable header based traffic routing +``` + +## Traffic routing mirroring traffic to canary +##### Traffic router support: (Istio) + +Argo Rollouts has ability to mirror traffic to the canary-service based on a various matching rules. +The step for the mirror based traffic routing is `setMirrorRoute` and has a list of matchers for the header. + +`name` - name of the mirror route. + +`percentage` - what percentage of the matched traffic to mirror + +`match` - The matching rules for the header route, if this is missing it acts as a removal of the route. +All conditions inside a single match block have AND semantics, while the list of match blocks have OR semantics. +Each type within a match (method, path, headers) must have one and only one match type (exact, regex, prefix) +Not all match types (exact, regex, prefix) will be supported by all traffic routers. + +To disable mirror based traffic route you just need to specify a `setMirrorRoute` with only the name of the route. + +This example will mirror 35% of HTTP traffic that matches a `GET` requests and with the url prefix of `/` +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +spec: + ... + strategy: + canary: + canaryService: canary-service + stableService: stable-service + trafficRouting: + managedRoutes: + - name: mirror-route + istio: + virtualService: + name: rollouts-demo-vsvc + steps: + - setCanaryScale: + weight: 25 + - setMirrorRoute: + name: mirror-route + percentage: 35 + match: + - method: + exact: GET + path: + prefix: / + - pause: + duration: 10m + - setMirrorRoute: + name: "mirror-route" # removes mirror based traffic route ``` \ No newline at end of file diff --git a/docs/features/traffic-management/nginx.md b/docs/features/traffic-management/nginx.md index ae87db0d30..414c91cc46 100644 --- a/docs/features/traffic-management/nginx.md +++ b/docs/features/traffic-management/nginx.md @@ -39,6 +39,6 @@ Since the Nginx Ingress controller allows users to configure the annotation pref ## Using Argo Rollouts with multiple NGINX ingress controllers -As a default, the Argo Rollouts controller only operates on ingresses with the `kubernetes.io/ingress.class` annotation set to `nginx`. A user can configure the controller to operate on Ingresses with different `kubernetes.io/ingress.class` values by specifying the `--nginx-ingress-classes` flag. A user can list the `--nginx-ingress-classes` flag multiple times if the Argo Rollouts controller should operate on multiple values. This solves the case where a cluster has multiple Ingress controllers operating on different `kubernetes.io/ingress.class` values. +As a default, the Argo Rollouts controller only operates on ingresses with the `kubernetes.io/ingress.class` annotation or `spec.ingressClassName` set to `nginx`. A user can configure the controller to operate on Ingresses with different class name by specifying the `--nginx-ingress-classes` flag. A user can list the `--nginx-ingress-classes` flag multiple times if the Argo Rollouts controller should operate on multiple values. This solves the case where a cluster has multiple Ingress controllers operating on different class values. -If the user would like the controller to operate on any Ingress without the `kubernetes.io/ingress.class` annotation, a user should add the following `--nginx-ingress-classes ''`. \ No newline at end of file +If the user would like the controller to operate on any Ingress without the `kubernetes.io/ingress.class` annotation or `spec.ingressClassName`, a user should add the following `--nginx-ingress-classes ''`. diff --git a/docs/index.md b/docs/index.md index 0174ed9f48..1ac7a815ba 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ # Argo Rollouts - Kubernetes Progressive Delivery Controller ## What is Argo Rollouts? -Argo Rollouts is a [Kubernetes controller](https://kubernetes.io/docs/concepts/architecture/controller/) and set of [CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes. +Argo Rollouts is a [Kubernetes controller](https://kubernetes.io/docs/concepts/architecture/controller/) and set of [CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) which provide advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes. Argo Rollouts (optionally) integrates with [ingress controllers](https://kubernetes.io/docs/concepts/services-networking/ingress/) and service meshes, leveraging their traffic shaping abilities to gradually shift traffic to the new version during an update. Additionally, Rollouts can query and interpret metrics from various providers to verify key KPIs and drive automated promotion or rollback during an update. @@ -30,7 +30,7 @@ For these reasons, in large scale high-volume production environments, a rolling * Ingress controller integration: NGINX, ALB * Service Mesh integration: Istio, Linkerd, SMI * Simultaneous usage of multiple providers: SMI + NGINX, Istio + ALB, etc. -* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic, Graphite +* Metric provider integration: Prometheus, Wavefront, Kayenta, Web, Kubernetes Jobs, Datadog, New Relic, Graphite, InfluxDB ## Quick Start @@ -39,12 +39,12 @@ kubectl create namespace argo-rollouts kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml ``` -Follow the full [getting started guide](getting-started.md) to walk through creating and then updating a rollout object. +Follow the full [getting started guide](getting-started.md) to walk through creating and then updating a rollout object. ## How does it work? -Similar to the [deployment object](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), the Argo Rollouts controller will manage the creation, scaling, and deletion of [ReplicaSets](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/). These ReplicaSets are defined by the `spec.template` field inside the Rollout resource, which uses the same pod template as the deployment object. +Similar to the [deployment object](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/), the Argo Rollouts controller will manage the creation, scaling, and deletion of [ReplicaSets](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/). These ReplicaSets are defined by the `spec.template` field inside the Rollout resource, which uses the same pod template as the deployment object. -When the `spec.template` is changed, that signals to the Argo Rollouts controller that a new ReplicaSet will be introduced. The controller will use the strategy set within the `spec.strategy` field in order to determine how the rollout will progress from the old ReplicaSet to the new ReplicaSet. Once that new ReplicaSet is scaled up (and optionally passes an [Analysis](features/analysis/)), the controller will mark it as "stable". +When the `spec.template` is changed, that signals to the Argo Rollouts controller that a new ReplicaSet will be introduced. The controller will use the strategy set within the `spec.strategy` field in order to determine how the rollout will progress from the old ReplicaSet to the new ReplicaSet. Once that new ReplicaSet is scaled up (and optionally passes an [Analysis](features/analysis/)), the controller will mark it as "stable". If another change occurs in the `spec.template` during a transition from a stable ReplicaSet to a new ReplicaSet (i.e. you change the application version in the middle of a rollout), then the previously new ReplicaSet will be scaled down, and the controller will try to progress the ReplicasSet that reflects the updated `spec.template` field. There is more information on the behaviors of each strategy in the [spec](features/specification/) section. @@ -60,7 +60,7 @@ If another change occurs in the `spec.template` during a transition from a stabl - A user wants to use the normal Rolling Update strategy from the deployment. If a user uses the canary strategy with no steps, the rollout will use the max surge and max unavailable values to roll to the new version. ([example](https://github.com/argoproj/argo-rollouts/blob/master/examples/rollout-rolling-update.yaml)) -## Examples +## Examples You can see more examples of Rollouts at: diff --git a/docs/releasing.md b/docs/releasing.md index 4e5b527302..a72f1308e6 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -19,14 +19,15 @@ 1. Update Brew formula: + * Fork the repo https://github.com/argoproj/homebrew-tap + * Run the following commands to update the brew formula: ```bash - git clone git@github.com:argoproj/homebrew-tap.git cd homebrew-tap - git pull ./update.sh kubectl-argo-rollouts $VERSION git commit -am "Update kubectl-argo-rollouts to $VERSION" - git push ``` + * Create a PR with the modified files pointing to upstream/master + * Once the PR is approved by a maintainer, it can be merged. ### Verify diff --git a/examples/dashboard.json b/examples/dashboard.json index 05f60be4f6..b73151f669 100644 --- a/examples/dashboard.json +++ b/examples/dashboard.json @@ -28,7 +28,7 @@ "rgba(237, 129, 40, 0.89)", "#d44a3a" ], - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "custom": {} @@ -107,7 +107,7 @@ }, { "cacheTimeout": null, - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -185,7 +185,7 @@ "cacheTimeout": null, "dashLength": 10, "dashes": false, - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "custom": {} @@ -279,7 +279,7 @@ }, { "collapsed": true, - "datasource": null, + "datasource": "$datasource", "gridPos": { "h": 1, "w": 24, @@ -290,7 +290,7 @@ "panels": [ { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -370,7 +370,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -450,7 +450,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -530,7 +530,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -607,7 +607,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -687,7 +687,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -764,7 +764,7 @@ }, { "cacheTimeout": null, - "datasource": "Prometheus", + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -846,7 +846,7 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "custom": {} @@ -952,7 +952,7 @@ "mode": "spectrum" }, "dataFormat": "tsbuckets", - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "custom": {} @@ -1014,7 +1014,7 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "custom": {} @@ -1112,7 +1112,7 @@ }, { "collapsed": true, - "datasource": null, + "datasource": "$datasource", "gridPos": { "h": 1, "w": 24, @@ -1122,7 +1122,7 @@ "id": 32, "panels": [ { - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -1186,7 +1186,7 @@ "type": "table" }, { - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -1246,7 +1246,7 @@ "type": "stat" }, { - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -1309,7 +1309,7 @@ "type": "stat" }, { - "datasource": null, + "datasource": "$datasource", "fieldConfig": { "defaults": { "color": { @@ -1382,6 +1382,25 @@ "tags": [], "templating": { "list": [ + { + "current": { + "selected": false, + "text": "prometheus", + "value": "prometheus" + }, + "hide": 0, + "definition": "datasource(prometheus)", + "description": "rollout datasource", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, { "allValue": null, "current": { @@ -1389,7 +1408,7 @@ "text": "default", "value": "default" }, - "datasource": null, + "datasource": "$datasource", "definition": "label_values(rollout_info,namespace)", "description": "rollout namespace", "error": null, @@ -1420,7 +1439,7 @@ "text": "", "value": "" }, - "datasource": null, + "datasource": "$datasource", "definition": "label_values(rollout_info{namespace=\"$rollout_namespace\"},name)", "description": "rollout name", "error": null, diff --git a/examples/traffic-routing/istio-mirror.yaml b/examples/traffic-routing/istio-mirror.yaml new file mode 100644 index 0000000000..d56c36add4 --- /dev/null +++ b/examples/traffic-routing/istio-mirror.yaml @@ -0,0 +1,117 @@ +## This examples sets up istio mirroring if running locally using docker for destkop you can add +## istio-host-split.com to your /etc/hosts and point it to 127.0.0.1 to view demo. +apiVersion: v1 +kind: Service +metadata: + name: istio-host-split-canary +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: istio-host-split + +--- +apiVersion: v1 +kind: Service +metadata: + name: istio-host-split-stable +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: istio-host-split + +--- +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: istio-host-split-vsvc +spec: + hosts: + - istio-host-split.com + gateways: + - istio-host-split-gateway + http: + - name: primary + route: + - destination: + host: istio-host-split-stable + weight: 100 + - destination: + host: istio-host-split-canary + weight: 0 + +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: istio-host-split +spec: + replicas: 4 + strategy: + canary: + canaryService: istio-host-split-canary + stableService: istio-host-split-stable + trafficRouting: + managedRoutes: + - name: mirror-route + istio: + virtualService: + name: istio-host-split-vsvc + routes: + - primary + steps: + - setCanaryScale: + weight: 50 + - setMirrorRoute: + name: mirror-route + percentage: 50 + match: + - method: + exact: POST + path: + prefix: /color + - pause: {} + selector: + matchLabels: + app: istio-host-split + template: + metadata: + labels: + app: istio-host-split + spec: + containers: + - name: istio-host-split + image: argoproj/rollouts-demo:green + ports: + - name: http + containerPort: 8080 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + +--- + +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: istio-host-split-gateway +spec: + selector: + istio: ingressgateway # use istio default controller + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "istio-host-split.com" + diff --git a/experiments/controller.go b/experiments/controller.go index 76d3841cc2..2b26005971 100644 --- a/experiments/controller.go +++ b/experiments/controller.go @@ -174,6 +174,11 @@ func NewController(cfg ControllerConfig) *Controller { controllerutil.Enqueue(obj, cfg.RolloutWorkQueue) } controllerutil.EnqueueParentObject(obj, register.RolloutKind, enqueueRollout) + if ex := unstructuredutil.ObjectToExperiment(obj); ex != nil { + logCtx := logutil.WithExperiment(ex) + logCtx.Info("experiment deleted") + controller.metricsServer.Remove(ex.Namespace, ex.Name, logutil.ExperimentKey) + } }, }) diff --git a/go.mod b/go.mod index 0a0e391e9b..ca2591fc13 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,15 @@ module github.com/argoproj/argo-rollouts -go 1.17 +go 1.18 require ( github.com/antonmedv/expr v1.9.0 github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec - github.com/argoproj/pkg v0.9.0 - github.com/aws/aws-sdk-go-v2 v1.13.0 - github.com/aws/aws-sdk-go-v2/config v1.13.1 - github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.15.0 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.16.0 - github.com/aws/smithy-go v1.10.0 + github.com/argoproj/pkg v0.13.6 + github.com/aws/aws-sdk-go-v2 v1.16.7 + github.com/aws/aws-sdk-go-v2/config v1.15.14 + github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.6 + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.8 github.com/blang/semver v3.5.1+incompatible github.com/evanphx/json-patch/v5 v5.6.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 @@ -18,189 +17,189 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/grpc-gateway v1.16.0 - github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a - github.com/mitchellh/mapstructure v1.4.3 - github.com/newrelic/newrelic-client-go v0.72.0 + github.com/influxdata/influxdb-client-go/v2 v2.9.1 + github.com/juju/ansiterm v1.0.0 + github.com/mitchellh/mapstructure v1.5.0 + github.com/newrelic/newrelic-client-go v0.86.5 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.12.1 + github.com/prometheus/client_golang v1.12.2-0.20220620141757-4ad265f1b4ee github.com/prometheus/client_model v0.2.0 - github.com/prometheus/common v0.32.1 - github.com/servicemeshinterface/smi-sdk-go v0.4.1 + github.com/prometheus/common v0.36.0 + github.com/servicemeshinterface/smi-sdk-go v0.5.0 github.com/sirupsen/logrus v1.8.1 github.com/soheilhy/cmux v0.1.5 github.com/spaceapegames/go-wavefront v1.8.1 - github.com/spf13/cobra v1.3.0 - github.com/stretchr/testify v1.7.0 + github.com/spf13/cobra v1.5.0 + github.com/stretchr/testify v1.8.0 github.com/tj/assert v0.0.3 github.com/valyala/fasttemplate v1.2.1 - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa - google.golang.org/grpc v1.42.0 - google.golang.org/protobuf v1.27.1 + google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d + google.golang.org/grpc v1.47.0 + google.golang.org/protobuf v1.28.0 gopkg.in/yaml.v2 v2.4.0 - k8s.io/api v0.23.3 - k8s.io/apiextensions-apiserver v0.23.1 - k8s.io/apimachinery v0.23.3 - k8s.io/apiserver v0.23.1 - k8s.io/cli-runtime v0.23.1 - k8s.io/client-go v0.23.3 - k8s.io/code-generator v0.23.2-rc.0 - k8s.io/component-base v0.23.1 - k8s.io/klog/v2 v2.30.0 - k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 - k8s.io/kubectl v0.23.1 - k8s.io/kubernetes v1.23.1 - k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b + k8s.io/api v0.24.2 + k8s.io/apiextensions-apiserver v0.24.2 + k8s.io/apimachinery v0.24.2 + k8s.io/apiserver v0.24.2 + k8s.io/cli-runtime v0.24.2 + k8s.io/client-go v0.24.2 + k8s.io/code-generator v0.24.2 + k8s.io/component-base v0.24.2 + k8s.io/klog/v2 v2.70.1 + k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 + k8s.io/kubectl v0.24.2 + k8s.io/kubernetes v1.24.2 + k8s.io/utils v0.0.0-20220706174534-f6158b442e7c + ) require ( - cloud.google.com/go v0.99.0 // indirect + cloud.google.com/go/compute v1.7.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.18 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect - github.com/Masterminds/goutils v1.1.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 // indirect + github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20220708192748-b73dcb041214 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.12.9 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.9 // indirect + github.com/aws/smithy-go v1.12.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect - github.com/cyphar/filepath-securejoin v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/emicklei/go-restful v2.9.5+incompatible // indirect - github.com/evanphx/json-patch v4.12.0+incompatible // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect - github.com/go-errors/errors v1.0.1 // indirect - github.com/go-logr/logr v1.2.0 // indirect + github.com/deepmap/oapi-codegen v1.11.0 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.4.0 // indirect - github.com/golang-jwt/jwt/v4 v4.0.0 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.21.1 // indirect + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.6 // indirect + github.com/google/btree v1.1.2 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/go-github/v41 v41.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/gofuzz v1.1.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.1.2 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/gregdel/pushover v1.1.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lunixbochs/vtclean v1.0.0 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mitchellh/copystructure v1.0.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/runc v1.0.2 // indirect - github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5 // indirect + github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.13 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/russross/blackfriday v1.5.2 // indirect - github.com/slack-go/slack v0.10.1 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/slack-go/slack v0.11.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fastjson v1.6.3 // indirect github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0 // indirect github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect - golang.org/x/mod v0.5.1 // indirect - golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect + golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect + golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect + golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb // indirect + golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - golang.org/x/tools v0.1.9 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect + golang.org/x/tools v0.1.10 // indirect + golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45 // indirect - gomodules.xyz/notify v0.1.0 // indirect + gomodules.xyz/notify v0.1.1 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/cluster-bootstrap v0.0.0 // indirect - k8s.io/component-helpers v0.23.1 // indirect - k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect - sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect - sigs.k8s.io/kustomize/api v0.10.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/cluster-bootstrap v0.24.2 // indirect + k8s.io/component-helpers v0.24.2 // indirect + k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect + sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect + sigs.k8s.io/kustomize/api v0.11.5 // indirect + sigs.k8s.io/kustomize/kyaml v0.13.7 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) replace ( github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127 - github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0 - github.com/miekg/dns => github.com/miekg/dns v1.1.45 - k8s.io/api => k8s.io/api v0.23.1 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.23.1 - k8s.io/apimachinery => k8s.io/apimachinery v0.23.2-rc.0 - k8s.io/apiserver => k8s.io/apiserver v0.23.1 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.23.1 - k8s.io/client-go => k8s.io/client-go v0.23.1 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.23.1 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.23.1 - k8s.io/code-generator => k8s.io/code-generator v0.23.2-rc.0 - k8s.io/component-base => k8s.io/component-base v0.23.1 - k8s.io/component-helpers => k8s.io/component-helpers v0.23.1 - k8s.io/controller-manager => k8s.io/controller-manager v0.23.1 - k8s.io/cri-api => k8s.io/cri-api v0.23.2-rc.0 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.23.1 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.23.1 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.23.1 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.23.1 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.23.1 - k8s.io/kubectl => k8s.io/kubectl v0.23.1 - k8s.io/kubelet => k8s.io/kubelet v0.23.1 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.23.1 - k8s.io/metrics => k8s.io/metrics v0.23.1 - k8s.io/mount-utils => k8s.io/mount-utils v0.23.2-rc.0 - k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.23.1 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.23.1 - k8s.io/sample-cli-plugin => k8s.io/sample-cli-plugin v0.23.1 - k8s.io/sample-controller => k8s.io/sample-controller v0.23.1 + k8s.io/api v0.0.0 => k8s.io/api v0.24.2 + k8s.io/apiextensions-apiserver v0.0.0 => k8s.io/apiextensions-apiserver v0.24.2 + k8s.io/apimachinery v0.0.0 => k8s.io/apimachinery v0.24.2 + k8s.io/apiserver v0.0.0 => k8s.io/apiserver v0.24.2 + k8s.io/cli-runtime v0.0.0 => k8s.io/cli-runtime v0.24.2 + k8s.io/client-go v0.0.0 => k8s.io/client-go v0.24.2 + k8s.io/cloud-provider v0.0.0 => k8s.io/cloud-provider v0.24.2 + k8s.io/cluster-bootstrap v0.0.0 => k8s.io/cluster-bootstrap v0.24.2 + k8s.io/code-generator v0.0.0 => k8s.io/code-generator v0.24.2 + k8s.io/component-base v0.0.0 => k8s.io/component-base v0.24.2 + k8s.io/component-helpers v0.0.0 => k8s.io/component-helpers v0.24.2 + k8s.io/controller-manager v0.0.0 => k8s.io/controller-manager v0.24.2 + k8s.io/cri-api v0.0.0 => k8s.io/cri-api v0.24.2 + k8s.io/csi-translation-lib v0.0.0 => k8s.io/csi-translation-lib v0.24.2 + k8s.io/kube-aggregator v0.0.0 => k8s.io/kube-aggregator v0.24.2 + k8s.io/kube-controller-manager v0.0.0 => k8s.io/kube-controller-manager v0.24.2 + k8s.io/kube-proxy v0.0.0 => k8s.io/kube-proxy v0.24.2 + k8s.io/kube-scheduler v0.0.0 => k8s.io/kube-scheduler v0.24.2 + k8s.io/kubectl v0.0.0 => k8s.io/kubectl v0.24.2 + k8s.io/kubelet v0.0.0 => k8s.io/kubelet v0.24.2 + k8s.io/legacy-cloud-providers v0.0.0 => k8s.io/legacy-cloud-providers v0.24.2 + k8s.io/metrics v0.0.0 => k8s.io/metrics v0.24.2 + k8s.io/mount-utils v0.0.0 => k8s.io/mount-utils v0.24.2 + k8s.io/pod-security-admission v0.0.0 => k8s.io/pod-security-admission v0.24.2 + k8s.io/sample-apiserver v0.0.0 => k8s.io/sample-apiserver v0.24.2 ) diff --git a/go.sum b/go.sum index f45bf974f5..a2f53996ca 100644 --- a/go.sum +++ b/go.sum @@ -28,18 +28,27 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= -cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -49,27 +58,37 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -80,10 +99,12 @@ github.com/GoogleCloudPlatform/k8s-cloud-provider v1.16.1-0.20210702024009-ea616 github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= @@ -94,12 +115,13 @@ github.com/Microsoft/hcsshim v0.8.22/go.mod h1:91uVCVzvX2QD16sMCenoxxXo6L1wJnLMX github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60 h1:prBTRx78AQnXzivNT9Crhu564W/zPPr3ibSlpT9xKcE= github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA= +github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20220708192748-b73dcb041214 h1:MdZskg1II+YVe+9ss935i8+paqqf4KEuYcTYUWSwABI= +github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20220708192748-b73dcb041214/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -112,47 +134,50 @@ github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmH github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU= github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= github.com/appscode/go v0.0.0-20190808133642-1d4ef1f1c1e0/go.mod h1:iy07dV61Z7QQdCKJCIvUoDL21u6AIceRhZzyleh2ymc= +github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE= github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec h1:ulv8ieYQZLyQrTVR4za1ucLFnemS0Dksz8y5e91xxak= github.com/argoproj/notifications-engine v0.3.1-0.20220129012210-32519f8f68ec/go.mod h1:QF4tr3wfWOnhkKSaRpx7k/KEErQAh8iwKQ2pYFu/SfA= -github.com/argoproj/pkg v0.9.0 h1:PfWWYykfcEQdN0g41XLbVh/aonTjD+dPkvDp3hwpLYM= -github.com/argoproj/pkg v0.9.0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA= +github.com/argoproj/pkg v0.13.6 h1:36WPD9MNYECHcO1/R1pj6teYspiK7uMQLCgLGft2abM= +github.com/argoproj/pkg v0.13.6/go.mod h1:I698DoJBKuvNFaixh4vFl2C88cNIT1WS7KCbz5ewyF8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM= -github.com/aws/aws-sdk-go v1.33.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go-v2 v1.13.0 h1:1XIXAfxsEmbhbj5ry3D3vX+6ZcUYvIqSm4CWWEuGZCA= -github.com/aws/aws-sdk-go-v2 v1.13.0/go.mod h1:L6+ZpqHaLbAaxsqV0L4cvxZY7QupWJB4fhkf8LXvC7w= -github.com/aws/aws-sdk-go-v2/config v1.13.1 h1:yLv8bfNoT4r+UvUKQKqRtdnvuWGMK5a82l4ru9Jvnuo= -github.com/aws/aws-sdk-go-v2/config v1.13.1/go.mod h1:Ba5Z4yL/UGbjQUzsiaN378YobhFo0MLfueXGiOsYtEs= -github.com/aws/aws-sdk-go-v2/credentials v1.8.0 h1:8Ow0WcyDesGNL0No11jcgb1JAtE+WtubqXjgxau+S0o= -github.com/aws/aws-sdk-go-v2/credentials v1.8.0/go.mod h1:gnMo58Vwx3Mu7hj1wpcG8DI0s57c9o42UQ6wgTQT5to= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 h1:NITDuUZO34mqtOwFWZiXo7yAHj7kf+XPE+EiKuCBNUI= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0/go.mod h1:I6/fHT/fH460v09eg2gVrd8B/IqskhNdpcLH0WNO3QI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4 h1:CRiQJ4E2RhfDdqbie1ZYDo8QtIo75Mk7oTdJSfwJTMQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4/go.mod h1:XHgQ7Hz2WY2GAn//UXHofLfPXWh+s62MbMOijrg12Lw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0 h1:3ADoioDMOtF4uiK59vCpplpCwugEU+v4ZFD29jDL3RQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.2.0/go.mod h1:BsCSJHx5DnDXIrOcqB8KN1/B+hXLG/bi4Y6Vjcx/x9E= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5 h1:ixotxbfTCFpqbuwFv/RcZwyzhkxPSYDYEMcj4niB5Uk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.5/go.mod h1:R3sWUqPcfXSiF/LSFJhjyJmpg9uV6yP2yv3YZZjldVI= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.15.0 h1:5WstmcviZ9X/h5nORkGT4akyLmWjrLxE9s8oKkFhkD4= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.15.0/go.mod h1:bPS4S6vXEGUVMabXYHOJRFvoWrztb38v4i84i8Hd6ZY= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.16.0 h1:4NawSD1qP7RPEqtCoahFNwkTa4MHtoKF08mhy+Y2Kok= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.16.0/go.mod h1:5rsn/Fxs9Rnq28KLB8n1pJcRR3UtrHY787uapxrvDRA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0 h1:4QAOB3KrvI1ApJK14sliGr3Ie2pjyvNypn/lfzDHfUw= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.7.0/go.mod h1:K/qPe6AP2TGYv4l6n7c88zh9jWBDf6nHhvg1fx/EWfU= -github.com/aws/aws-sdk-go-v2/service/sso v1.9.0 h1:1qLJeQGBmNQW3mBNzK2CFmrQNmoXWrscPqsrAaU1aTA= -github.com/aws/aws-sdk-go-v2/service/sso v1.9.0/go.mod h1:vCV4glupK3tR7pw7ks7Y4jYRL86VvxS+g5qk04YeWrU= -github.com/aws/aws-sdk-go-v2/service/sts v1.14.0 h1:ksiDXhvNYg0D2/UFkLejsaz3LqpW5yjNQ8Nx9Sn2c0E= -github.com/aws/aws-sdk-go-v2/service/sts v1.14.0/go.mod h1:u0xMJKDvvfocRjiozsoZglVNXRG19043xzp3r2ivLIk= -github.com/aws/smithy-go v1.10.0 h1:gsoZQMNHnX+PaghNw4ynPsyGP7aUCqx5sY2dlPQsZ0w= -github.com/aws/smithy-go v1.10.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/aws-sdk-go v1.44.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.16.7 h1:zfBwXus3u14OszRxGcqCDS4MfMCv10e8SMJ2r8Xm0Ns= +github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= +github.com/aws/aws-sdk-go-v2/config v1.15.14 h1:+BqpqlydTq4c2et9Daury7gE+o67P4lbk7eybiCBNc4= +github.com/aws/aws-sdk-go-v2/config v1.15.14/go.mod h1:CQBv+VVv8rR5z2xE+Chdh5m+rFfsqeY4k0veEZeq6QM= +github.com/aws/aws-sdk-go-v2/credentials v1.12.9 h1:DloAJr0/jbvm0iVRFDFh8GlWxrOd9XKyX82U+dfVeZs= +github.com/aws/aws-sdk-go-v2/credentials v1.12.9/go.mod h1:2Vavxl1qqQXJ8MUcQZTsIEW8cwenFCWYXtLRPba3L/o= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8 h1:VfBdn2AxwMbFyJN/lF/xuT3SakomJ86PZu3rCxb5K0s= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8/go.mod h1:oL1Q3KuCq1D4NykQnIvtRiBGLUXhcpY5pl6QZB2XEPU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14 h1:2C0pYHcUBmdzPj+EKNC4qj97oK6yjrUhc1KoSodglvk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8 h1:2J+jdlBJWEmTyAwC82Ym68xCykIvnSnIN18b8xHGlcc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15 h1:QquxR7NH3ULBsKC+NoTpilzbKKS+5AELfNREInbhvas= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15/go.mod h1:Tkrthp/0sNBShQQsamR7j/zY4p19tVTAs+nnqhH6R3c= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.6 h1:3FtKgndLdv919p3V4VStk8y3agcC9yEu9vrhhe+rvfQ= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.18.6/go.mod h1:A9gdtslk61CskUB2nDcY2fuvJ1RNl5bskr1eTJrcUJU= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.8 h1:D6Sc+XyjK++NhkJJLvZNcf0xyzNhhC+GVn/MYDeONS4= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.8/go.mod h1:8OyausC7+VUBNJFOEDjvSrowuefSkEoJaUzsGLNRXZQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8 h1:oKnAXxSF2FUvfgw8uzU/v9OTYorJJZ8eBmWhr9TWVVQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8/go.mod h1:rDVhIMAX9N2r8nWxDUlbubvvaFMnfsm+3jAV7q+rpM4= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.12 h1:760bUnTX/+d693FT6T6Oa7PZHfEQT9XMFZeM5IQIB0A= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.12/go.mod h1:MO4qguFjs3wPGcCSpQ7kOFTwRvb+eu+fn+1vKleGHUk= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.9 h1:yOfILxyjmtr2ubRkRJldlHDFBhf5vw4CzhbwWIBmimQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.9/go.mod h1:O1IvkYxr+39hRf960Us6j0x1P8pDqhTX+oXM5kQNl/Y= +github.com/aws/smithy-go v1.12.0 h1:gXpeZel/jPoWQ7OEmLIgCUnhkFftqNfwWUwAHSlp1v0= +github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -161,34 +186,34 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradleyfalzon/ghinstallation/v2 v2.0.4 h1:tXKVfhE7FcSkhkv0UwkLvPDeZ4kz6OXd0PKPlFqf81M= github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -212,14 +237,14 @@ github.com/container-storage-interface/spec v1.5.0/go.mod h1:8K96oQNkJ7pFcC2R9Z1 github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.11/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.12/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/coredns/caddy v1.1.0/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= github.com/coredns/corefile-migration v1.0.14/go.mod h1:XnhgULOEouimnzgn0t4WPuFDN2/PJQcTxdWKC5eXNGE= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= @@ -235,34 +260,42 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.11.0 h1:f/X2NdIkaBKsSdpeuwLnY/vDI0AtPUrmB5LMgc7YD+A= +github.com/deepmap/oapi-codegen v1.11.0/go.mod h1:k+ujhoQGxmQYBZBbxhOZNZf4j08qv5mC+OH+fFTnKxM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/distribution v2.8.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 h1:lS3P5Nw3oPO05Lk2gFiYUOL3QPaH+fRoI1wFOc4G1UY= -github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/elazarl/goproxy v0.0.0-20220417044921-416226498f94 h1:VIy7cdK7ufs7ctpTFkXJHm1uP3dJSnCGSPysEICB1so= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -272,16 +305,21 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= @@ -290,13 +328,14 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flosch/pongo2 v0.0.0-20181225140029-79872a7b2769/go.mod h1:tbAXHifHQWNSpWbiJHpJTZH5fi3XHhDMdP//vuz9WS4= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -307,59 +346,93 @@ github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72 github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.4.0 h1:Mr3JcvBjQEhCN9wld6OHKHuHxWaoXTaQfYKmj7QwP18= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.4.0/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -376,6 +449,7 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -395,14 +469,19 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cadvisor v0.43.0/go.mod h1:+RdMSbc3FVr5NYCD2dOEJy/LI0jYJ/0xJXkzWXEyiFQ= -github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cadvisor v0.44.1/go.mod h1:GQ9KQfz0iNHQk3D6ftzJWK4TXabfIgM10Oy3FkR+Gzg= +github.com/google/cel-go v0.10.1/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -414,16 +493,19 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v41 v41.0.0 h1:HseJrM2JFf2vfiZJ8anY2hqBjdfY1Vlj/K27ueww4gg= github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -446,16 +528,23 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo= github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= @@ -464,8 +553,9 @@ github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORR github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregdel/pushover v1.1.0 h1:dwHyvrcpZCOS9V1fAnKPaGRRI5OC55cVaKhMybqNsKQ= github.com/gregdel/pushover v1.1.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -474,6 +564,7 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -496,8 +587,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -531,14 +622,17 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb-client-go/v2 v2.9.1 h1:5kbH226fmmiV0MMTs7a8L7/ECCKdJWBi1QZNNv4/TkI= +github.com/influxdata/influxdb-client-go/v2 v2.9.1/go.mod h1:x7Jo5UHHl+w8wu8UnGiNobDDHygojXwJX4mx7rXGKMk= +github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= +github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/ishidawataru/sctp v0.0.0-20190723014705-7c296d48a2b5/go.mod h1:DM4VvS+hD/kDi1U1QsX2fnZowwBhqD0Dk3bRPKF/Oc8= github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -551,6 +645,7 @@ github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFF github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -559,8 +654,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= -github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/juju/ansiterm v1.0.0 h1:gmMvnZRq7JZJx6jkfSq9/+2LMrVEwGwt7UR6G+lmDEg= +github.com/juju/ansiterm v1.0.0/go.mod h1:PyXUpnI3olx3bsPcHt98FGPX/KCFZ1Fi+hw1XLI6384= github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= @@ -572,7 +667,9 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -581,10 +678,25 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.24/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= @@ -601,16 +713,21 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailgun/mailgun-go v2.0.0+incompatible/go.mod h1:NWTyU+O4aczg/nsGhQnvHL6v2n5Gy6Sv5tNDVvC6FbU= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.10/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -627,39 +744,43 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go/v7 v7.0.2/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6JUMSQ2zAns= +github.com/minio/minio-go/v7 v7.0.29/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= -github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -674,19 +795,20 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/newrelic/newrelic-client-go v0.72.0 h1:7dv4V+Wq4cKqZ1vq2OkspF9j08Q65D9A19LB6FWn9XM= -github.com/newrelic/newrelic-client-go v0.72.0/go.mod h1:VXjhsfui0rvhM9cVwnKwlidF8NbXlHZvh63ZKi6fImA= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/newrelic/newrelic-client-go v0.86.5 h1:RxjhA/xdjcnMTxl1oq+ms6tGTuZOOL+h8IcfBCD1PVY= +github.com/newrelic/newrelic-client-go v0.86.5/go.mod h1:RYMXt7hgYw7nzuXIGd2BH0F1AivgWw7WrBhNBQZEB4k= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nlopes/slack v0.5.0/go.mod h1:jVI4BBK3lSktibKahxBF74txcK2vyvkza1z/+rRnVAM= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= @@ -694,28 +816,34 @@ github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= +github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5 h1:AnS8ZCC5dle8P4X4FZ+IOlX9v0jAkCMiZDIzRnYwBbs= github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5/go.mod h1:f0ezb0R/mrB9Hpm5RrIS6EX3ydjsR2nAB88nYYXZcNY= +github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.13 h1:nV98dkBpqaYbDnhefmOQ+Rn4hE+jD6AtjYHXaU5WyJI= +github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.13/go.mod h1:4OjcxgwdXzezqytxN534MooNmrxRD50geWZxTD7845s= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -723,6 +851,7 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -740,8 +869,9 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.12.2-0.20220620141757-4ad265f1b4ee h1:8dyWwbEbKRZ13K6VEueQsOH2Ywu58j9YM/mn3bp50ww= +github.com/prometheus/client_golang v1.12.2-0.20220620141757-4ad265f1b4ee/go.mod h1:nDOYPpTKRWyFSHGWY5QbDUvjSMBusROfFzxhmDKUNWo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -753,9 +883,9 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo= +github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -771,24 +901,29 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/servicemeshinterface/smi-sdk-go v0.4.1 h1:L8nS7WtVlGoEJF7RdCbwh0Oj/JheGY+5fa3R+cA2ReY= -github.com/servicemeshinterface/smi-sdk-go v0.4.1/go.mod h1:9rsLPBNcqfDNmEgyYwpopn93aE9yz46d2EHFBNOYj/w= +github.com/servicemeshinterface/smi-sdk-go v0.5.0 h1:9cZdhvGbGDlmnp9qqmcQL+RL6KZ3IzHfDLoA5Axg8n0= +github.com/servicemeshinterface/smi-sdk-go v0.5.0/go.mod h1:nm1Slf3pfaZPP3g2tE/K5wDmQ1uWVSP0p3uu5rQAQLc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -797,11 +932,11 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA= github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ= +github.com/slack-go/slack v0.11.0 h1:sBBjQz8LY++6eeWhGJNZpRm5jvLRNnWBFZ/cAq58a6k= +github.com/slack-go/slack v0.11.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -815,7 +950,6 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -824,8 +958,10 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -841,16 +977,20 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/storageos/go-api v2.2.0+incompatible/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= @@ -861,11 +1001,17 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc= +github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= @@ -874,6 +1020,9 @@ github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1 github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0 h1:qqllXPzXh+So+mmANlX/gCJrgo+1kQyshMoQ+NASzm0= github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0/go.mod h1:2rx5KE5FLD0HRfkkpyn8JwbVLBdhgeiOb2D2D9LLKM4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= @@ -883,7 +1032,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= @@ -894,6 +1042,7 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= @@ -933,14 +1082,22 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= +golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -987,14 +1144,16 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1009,6 +1168,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1038,14 +1199,20 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8= -golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1064,8 +1231,12 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb h1:8tDJ3aechhddbdPAxpycgXHJRMLpk/Ab+aa4OgdN5/g= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1077,6 +1248,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1095,11 +1268,15 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1131,18 +1308,18 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1150,7 +1327,6 @@ golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1160,23 +1336,39 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s= +golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1190,12 +1382,17 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1209,11 +1406,14 @@ golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1249,6 +1449,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1257,19 +1458,23 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45 h1:juzzlx91nWAOsHuOVfXZPMXHtJEKouZvY9bBbwlOeYs= gomodules.xyz/envconfig v1.3.1-0.20190308184047-426f31af0d45/go.mod h1:41y72mzHT7+jFNgyBpJRrZWuZJcLmLrTpq6iGgOFJMQ= -gomodules.xyz/notify v0.1.0 h1:lN7CAFKIWxaXJXm3F/7KTbgw3lUy9peh6iyjgj1skvA= gomodules.xyz/notify v0.1.0/go.mod h1:wGy0vLXGpabCg0j9WbjzXf7pM7Khz11FqCLtBbTujP0= +gomodules.xyz/notify v0.1.1 h1:1tTuoyswmPvzqPCTEDQK8SZ3ukCxLsonAAwst2+y1a0= +gomodules.xyz/notify v0.1.1/go.mod h1:QgQyU4xEA/plJcDeT66J2Go2V7U4c0pD9wjo7HfFil4= +gomodules.xyz/version v0.1.0/go.mod h1:Y8xuV02mL/45psyPKG3NCVOwvAOy6T5Kx0l3rCjKSjU= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= @@ -1310,6 +1515,15 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1361,6 +1575,7 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= @@ -1385,8 +1600,28 @@ google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d h1:YbuF5+kdiC516xIP60RvlHeFbY9sRDR73QsAGHpkeVw= +google.golang.org/genproto v0.0.0-20220712132514-bdd2acd4974d/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1414,8 +1649,13 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1429,16 +1669,18 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= @@ -1452,10 +1694,12 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1468,8 +1712,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= @@ -1480,61 +1726,87 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= -k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= -k8s.io/apiextensions-apiserver v0.23.1 h1:xxE0q1vLOVZiWORu1KwNRQFsGWtImueOrqSl13sS5EU= -k8s.io/apiextensions-apiserver v0.23.1/go.mod h1:0qz4fPaHHsVhRApbtk3MGXNn2Q9M/cVWWhfHdY2SxiM= -k8s.io/apimachinery v0.23.2-rc.0 h1:MnclcThycinuHAhHBk1dAqEOYi85WlXeAbz2tfGjvWM= -k8s.io/apimachinery v0.23.2-rc.0/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= -k8s.io/apiserver v0.23.1 h1:vWGf8LcV9Pk/z5rdLmCiBDqE21ccbe930dzrtVMhw9g= -k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY= -k8s.io/cli-runtime v0.23.1 h1:vHUZrq1Oejs0WaJnxs09mLHKScvIIl2hMSthhS8o8Yo= -k8s.io/cli-runtime v0.23.1/go.mod h1:r9r8H/qfXo9w+69vwUL7LokKlLRKW5D6A8vUKCx+YL0= -k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= -k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= -k8s.io/cloud-provider v0.23.1/go.mod h1:kI8AnYwOSru5Bci8pPUWwV5kJMVkY1ICOp1p8KKZWpc= -k8s.io/cluster-bootstrap v0.23.1 h1:zHRsIwMSLWujGHlaXLglbwyiDOKFLXSGz7LTbAez3MM= -k8s.io/cluster-bootstrap v0.23.1/go.mod h1:p2732QxwSa13WPemmyIeykk16qVw15W7lgNRB6x7NpY= -k8s.io/code-generator v0.23.2-rc.0 h1:sG0zykJJ8CvByPs/chc8DThmILi607fnNfW+0YTaOZs= -k8s.io/code-generator v0.23.2-rc.0/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA= -k8s.io/component-base v0.23.1 h1:j/BqdZUWeWKCy2v/jcgnOJAzpRYWSbGcjGVYICko8Uc= -k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo= -k8s.io/component-helpers v0.23.1 h1:Xrtj0LwXUqYyTPvN2bOE2UcqURX+uSBmKX1koNGhVxI= -k8s.io/component-helpers v0.23.1/go.mod h1:ZK24U+2oXnBPcas2KolLigVVN9g5zOzaHLkHiQMFGr0= -k8s.io/controller-manager v0.23.1/go.mod h1:AFE4qIllvTh+nRwGr3SRSUt7F+xVSzXCeb0hhzYlU4k= -k8s.io/cri-api v0.23.2-rc.0/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= -k8s.io/csi-translation-lib v0.23.1/go.mod h1:0ZyB0cZBV4ZkqibwilEhKnxOne28rq5FDSjO+0MUVio= +k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0= +k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= +k8s.io/api v0.23.3/go.mod h1:w258XdGyvCmnBj/vGzQMj6kzdufJZVUwEM1U2fRJwSQ= +k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= +k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= +k8s.io/apiextensions-apiserver v0.24.2 h1:/4NEQHKlEz1MlaK/wHT5KMKC9UKYz6NZz6JE6ov4G6k= +k8s.io/apiextensions-apiserver v0.24.2/go.mod h1:e5t2GMFVngUEHUd0wuCJzw8YDwZoqZfJiGOW6mm2hLQ= +k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= +k8s.io/apimachinery v0.23.3/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= +k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= +k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/apiserver v0.24.2 h1:orxipm5elPJSkkFNlwH9ClqaKEDJJA3yR2cAAlCnyj4= +k8s.io/apiserver v0.24.2/go.mod h1:pSuKzr3zV+L+MWqsEo0kHHYwCo77AT5qXbFXP2jbvFI= +k8s.io/cli-runtime v0.24.2 h1:KxY6tSgPGsahA6c1/dmR3uF5jOxXPx2QQY6C5ZrLmtE= +k8s.io/cli-runtime v0.24.2/go.mod h1:1LIhKL2RblkhfG4v5lZEt7FtgFG5mVb8wqv5lE9m5qY= +k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY= +k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= +k8s.io/client-go v0.23.3/go.mod h1:47oMd+YvAOqZM7pcQ6neJtBiFH7alOyfunYN48VsmwE= +k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= +k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= +k8s.io/cloud-provider v0.24.2/go.mod h1:a7jyWjizk+IKbcIf8+mX2cj3NvpRv9ZyGdXDyb8UEkI= +k8s.io/cluster-bootstrap v0.24.2 h1:p177dIhDst4INUWBZgTnqSad8oJiUdKo0cLLVU24AzE= +k8s.io/cluster-bootstrap v0.24.2/go.mod h1:eIHV338K03vBm3u/ROZiNXxWJ4AJRoTR9PEUhcTvYkg= +k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= +k8s.io/code-generator v0.24.2 h1:EGeRWzJrpwi6T6CvoNl0spM6fnAnOdCr0rz7H4NU1rk= +k8s.io/code-generator v0.24.2/go.mod h1:dpVhs00hTuTdTY6jvVxvTFCk6gSMrtfRydbhZwHI15w= +k8s.io/component-base v0.24.2 h1:kwpQdoSfbcH+8MPN4tALtajLDfSfYxBDYlXobNWI6OU= +k8s.io/component-base v0.24.2/go.mod h1:ucHwW76dajvQ9B7+zecZAP3BVqvrHoOxm8olHEg0nmM= +k8s.io/component-helpers v0.24.2 h1:gtXmI/TjVINtkAdZn7m5p8+Vd0Mk4d1q8kwJMMLBdwY= +k8s.io/component-helpers v0.24.2/go.mod h1:TRQPBQKfmqkmV6c0HAmUs8cXVNYYYLsXy4zu8eODi9g= +k8s.io/controller-manager v0.24.2/go.mod h1:hpwCof4KxP4vrw/M5QiVxU6Zmmggmr1keGXtjGHF+vc= +k8s.io/cri-api v0.24.2/go.mod h1:t3tImFtGeStN+ES69bQUX9sFg67ek38BM9YIJhMmuig= +k8s.io/csi-translation-lib v0.24.2/go.mod h1:pdHc2CYLViQYYsOqOp79hjKYi8J4NZ7vpiVzn1SqBrg= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c h1:GohjlNKauSai7gN4wsJkeZ3WAJx4Sh+oT/b5IYn5suA= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 h1:TT1WdmqqXareKxZ/oNXEUSwKlLiHzPMyB0t8BaFeBYI= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-aggregator v0.23.1/go.mod h1:1SPZXYD/je2gKxxLBkYyG3yFxSCUWI5QTyjqP2ZxRDI= -k8s.io/kube-controller-manager v0.23.1/go.mod h1:KbZeNSFsBM5j2ddB5yXJ1nTQx2j/1/Cf7cXzG27aKZ0= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-aggregator v0.24.2/go.mod h1:Ju2jNDixn+vqeeKEBfjfpc204bO1pbdXX0N9knCxeMQ= +k8s.io/kube-controller-manager v0.24.2/go.mod h1:KDE0yqiEvxYiO0WRpPA4rVx8AcK1vsWydUF37AJ9lTI= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kube-proxy v0.23.1/go.mod h1:65QJpMrjUMHfgX1q5Pl/KqVRZBMM4qLHNMo5MhKsnp0= -k8s.io/kube-scheduler v0.23.1/go.mod h1:SFPvXnt7KlxTZILrtjH8VNwGDzXcdKKHrv4TkeZdYro= -k8s.io/kubectl v0.23.1 h1:gmscOiV4Y4XIRIn14gQBBADoyyVrDZPbxRCTDga4RSA= -k8s.io/kubectl v0.23.1/go.mod h1:Ui7dJKdUludF8yWAOSN7JZEkOuYixX5yF6E6NjoukKE= -k8s.io/kubelet v0.23.1/go.mod h1:WdvMiehtNPhtiW8sSVVvr8YYU00L0u+0HkfMDEB0LKM= -k8s.io/kubernetes v1.23.1 h1:iJfubd03CDap4m69Ue+u2I6quNUYiYlC8+TakEHATjc= -k8s.io/kubernetes v1.23.1/go.mod h1:baMGbPpwwP0kT/+eAPtdqoWNRoXyyTJ2Zf+fw/Y8t04= -k8s.io/legacy-cloud-providers v0.23.1/go.mod h1:HIt+r/ReEfjS6IGaGfpZ7tCna7hbMBXMOaIp/SWABVE= -k8s.io/metrics v0.23.1/go.mod h1:qXvsM1KANrc+ZZeFwj6Phvf0NLiC+d3RwcsLcdGc+xs= -k8s.io/mount-utils v0.23.2-rc.0/go.mod h1:9pFhzVjxle1osJUo++9MFDat9HPkQUOoHCn+eExZ3Ew= -k8s.io/pod-security-admission v0.23.1/go.mod h1:WDb/vFWf7jKSGe2e07LTEjDZ0MHMDhUIzXNvQ45HytU= -k8s.io/sample-apiserver v0.23.1/go.mod h1:5ZQrkouVpN6GeNMZEJFkIpFwaxgDPJin/cIBXyDboC4= -k8s.io/system-validators v1.6.0/go.mod h1:bPldcLgkIUK22ALflnsXk8pvkTEndYdNuaHH6gRrl0Q= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/kube-openapi v0.0.0-20220401212409-b28bf2818661/go.mod h1:daOouuuwd9JXpv1L7Y34iV3yf6nxzipkKMWWlqlvK9M= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8 h1:yEQKdMCjzAOvGeiTwG4hO/hNVNtDOuUFvMUZ0OlaIzs= +k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8/go.mod h1:mbJ+NSUoAhuR14N0S63bPkh8MGVSo3VYSGZtH/mfMe0= +k8s.io/kube-proxy v0.24.2/go.mod h1:bozS2ufl/Ns6s40Ue34eV7rqyLVygi5usSmCgW7rFU8= +k8s.io/kube-scheduler v0.24.2/go.mod h1:DRa+aeXKSYUUOHHIc/9EcaO9+FW5FydaOfPSvaSW5Ko= +k8s.io/kubectl v0.24.2 h1:+RfQVhth8akUmIc2Ge8krMl/pt66V7210ka3RE/p0J4= +k8s.io/kubectl v0.24.2/go.mod h1:+HIFJc0bA6Tzu5O/YcuUt45APAxnNL8LeMuXwoiGsPg= +k8s.io/kubelet v0.24.2/go.mod h1:Xm9DkWQjwOs+uGOUIIGIPMvvmenvj0lDVOErvIKOOt0= +k8s.io/kubernetes v1.24.2 h1:AyjtHzSysliKR04Km91njmk2yaKmOa3ZISQZCIGUnVI= +k8s.io/kubernetes v1.24.2/go.mod h1:8e8maMiZzBR2/8Po5Uulx+MXZUYJuN3vtKwD4Ct1Xi0= +k8s.io/legacy-cloud-providers v0.24.2/go.mod h1:sgkasgIP2ZOew8fzoOq0mQLVXJ4AmB57IUbFUjzPWEo= +k8s.io/metrics v0.24.2/go.mod h1:5NWURxZ6Lz5gj8TFU83+vdWIVASx7W8lwPpHYCqopMo= +k8s.io/mount-utils v0.24.2/go.mod h1:XrSqB3a2e8sq+aU+rlbcBtQ3EgcuDk5RP9ZsGxjoDrI= +k8s.io/pod-security-admission v0.24.2/go.mod h1:znnuDHWWWvh/tpbYYPwTsd4y//qHi3cOX+wGxET/mMI= +k8s.io/sample-apiserver v0.24.2/go.mod h1:mf8qgDdu450wqpCJOkSAmoTgU4PIMAcfa5uTBwmJekE= +k8s.io/system-validators v1.7.0/go.mod h1:gP1Ky+R9wtrSiFbrpEPwWMeYz9yqyy1S/KOh0Vci7WI= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b h1:wxEMGetGMur3J1xuGLQY7GEQYg9bZxKn3tKo5k/eYcs= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220706174534-f6158b442e7c h1:hFZO68mv/0xe8+V0gRT9BAq3/31cKjjeVv4nScriuBk= +k8s.io/utils v0.0.0-20220706174534-f6158b442e7c/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -1544,18 +1816,26 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.30/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= -sigs.k8s.io/kustomize/api v0.10.1 h1:KgU7hfYoscuqag84kxtzKdEC3mKMb99DPI3a0eaV1d0= -sigs.k8s.io/kustomize/api v0.10.1/go.mod h1:2FigT1QN6xKdcnGS2Ppp1uIWrtWN28Ms8A3OZUZhwr8= -sigs.k8s.io/kustomize/cmd/config v0.10.2/go.mod h1:K2aW7nXJ0AaT+VA/eO0/dzFLxmpFcTzudmAgDwPY1HQ= -sigs.k8s.io/kustomize/kustomize/v4 v4.4.1/go.mod h1:qOKJMMz2mBP+vcS7vK+mNz4HBLjaQSWRY22EF6Tb7Io= -sigs.k8s.io/kustomize/kyaml v0.13.0 h1:9c+ETyNfSrVhxvphs+K2dzT3dh5oVPPEqPOE/cUpScY= -sigs.k8s.io/kustomize/kyaml v0.13.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 h1:2sgAQQcY0dEW2SsQwTXhQV4vO6+rSslYx8K3XmM5hqQ= +sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/kustomize/api v0.11.4/go.mod h1:k+8RsqYbgpkIrJ4p9jcdPqe8DprLxFUUO0yNOq8C+xI= +sigs.k8s.io/kustomize/api v0.11.5 h1:vLDp++YAX7iy2y2CVPJNy9pk9CY8XaUKgHkjbVtnWag= +sigs.k8s.io/kustomize/api v0.11.5/go.mod h1:2UDpxS6AonWXow2ZbySd4AjUxmdXLeTlvGBC46uSiq8= +sigs.k8s.io/kustomize/cmd/config v0.10.6/go.mod h1:/S4A4nUANUa4bZJ/Edt7ZQTyKOY9WCER0uBS1SW2Rco= +sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVYCA4BzxeAaLI05Bg= +sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg= +sigs.k8s.io/kustomize/kyaml v0.13.7 h1:/EZ/nPaLUzeJKF/BuJ4QCuMVJWiEVoI8iftOHY3g3tk= +sigs.k8s.io/kustomize/kyaml v0.13.7/go.mod h1:6K+IUOuir3Y7nucPRAjw9yth04KSWBnP5pqUTGwj/qU= +sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/hack/gen-crd-spec/main.go b/hack/gen-crd-spec/main.go index 193de108d7..607f8ddd53 100644 --- a/hack/gen-crd-spec/main.go +++ b/hack/gen-crd-spec/main.go @@ -16,6 +16,7 @@ import ( "github.com/ghodss/yaml" extensionsobj "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kubeopenapiutil "k8s.io/kube-openapi/pkg/util" spec "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -112,12 +113,6 @@ func NewCustomResourceDefinition() []*extensionsobj.CustomResourceDefinition { removeK8S118Fields(obj) createMetadataValidation(obj) crd := toCRD(obj) - - if crd.Name == "clusteranalysistemplates.argoproj.io" { - crd.Spec.Scope = "Cluster" - } else { - crd.Spec.Scope = "Namespaced" - } crds = append(crds, crd) } @@ -398,9 +393,6 @@ func generateKustomizeSchema(crds []*extensionsobj.CustomResourceDefinition, out definitions := map[string]interface{}{} for _, crd := range crds { - if crd.Spec.Names.Kind != "Rollout" { - continue - } var version string var props map[string]extensionsobj.JSONSchemaProps for _, v := range crd.Spec.Versions { @@ -434,7 +426,8 @@ func generateKustomizeSchema(crds []*extensionsobj.CustomResourceDefinition, out } } - definitions[fmt.Sprintf("%s.%s", version, crd.Spec.Names.Kind)] = map[string]interface{}{ + definitionName := kubeopenapiutil.ToRESTFriendlyName(fmt.Sprintf("%s/%s.%s", crd.Spec.Group, version, crd.Spec.Names.Kind)) + definitions[definitionName] = map[string]interface{}{ "properties": propsMap, "x-kubernetes-group-version-kind": []map[string]string{{ "group": crd.Spec.Group, diff --git a/hack/installers/install-codegen-go-tools.sh b/hack/installers/install-codegen-go-tools.sh index 435838126c..8a08efd826 100755 --- a/hack/installers/install-codegen-go-tools.sh +++ b/hack/installers/install-codegen-go-tools.sh @@ -54,4 +54,4 @@ go install github.com/go-swagger/go-swagger/cmd/swagger@v0.28.0 go install golang.org/x/tools/cmd/goimports@v0.1.8 # mockery is used for generating mock -go install github.com/vektra/mockery/v2@v2.6.0 +go install github.com/vektra/mockery/v2@v2.14.0 diff --git a/hack/installers/install-dev-tools.sh b/hack/installers/install-dev-tools.sh new file mode 100755 index 0000000000..0e5f67f00a --- /dev/null +++ b/hack/installers/install-dev-tools.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -eux -o pipefail + +PROJECT_ROOT=$(cd $(dirname ${BASH_SOURCE})/../..; pwd) +DIST_PATH="${PROJECT_ROOT}/dist" +PATH="${DIST_PATH}:${PATH}" + +mkdir -p ${DIST_PATH} + +gotestsum_version=1.8.1 + +OS=$(go env GOOS) +ARCH=$(go env GOARCH) + +export TARGET_FILE=gotestsum_${gotestsum_version}_${OS}_${ARCH}.tar.gz +temp_path="/tmp/${TARGET_FILE}" +url=https://github.com/gotestyourself/gotestsum/releases/download/v${gotestsum_version}/gotestsum_${gotestsum_version}_${OS}_${ARCH}.tar.gz +[ -e ${temp_path} ] || curl -sLf --retry 3 -o ${temp_path} ${url} + +mkdir -p /tmp/gotestsum-${gotestsum_version} +tar -xvzf ${temp_path} -C /tmp/gotestsum-${gotestsum_version} +cp /tmp/gotestsum-${gotestsum_version}/gotestsum ${DIST_PATH}/gotestsum +chmod +x ${DIST_PATH}/gotestsum +gotestsum --version diff --git a/ingress/ingress.go b/ingress/ingress.go index bbe5f2f0d6..7bbf856b89 100644 --- a/ingress/ingress.go +++ b/ingress/ingress.go @@ -140,12 +140,7 @@ func (c *Controller) syncIngress(key string) error { if err != nil { return nil } - // An ingress without annotations cannot be a alb or nginx ingress - if ingress.GetAnnotations() == nil { - return nil - } - annotations := ingress.GetAnnotations() - class := annotations["kubernetes.io/ingress.class"] + class := ingress.GetClass() switch { case hasClass(c.albClasses, class): return c.syncALBIngress(ingress, rollouts) diff --git a/ingress/ingress_test.go b/ingress/ingress_test.go index 41461bbb02..88125b3206 100644 --- a/ingress/ingress_test.go +++ b/ingress/ingress_test.go @@ -24,6 +24,37 @@ import ( ) func newNginxIngress(name string, port int, serviceName string) *extensionsv1beta1.Ingress { + class := "nginx" + return &extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + }, + Spec: extensionsv1beta1.IngressSpec{ + IngressClassName: &class, + Rules: []extensionsv1beta1.IngressRule{ + { + Host: "fakehost.example.com", + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Path: "/foo", + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: serviceName, + ServicePort: intstr.FromInt(port), + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func newNginxIngressWithAnnotation(name string, port int, serviceName string) *extensionsv1beta1.Ingress { return &extensionsv1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -198,12 +229,9 @@ func TestSyncIngressReferencedByRollout(t *testing.T) { assert.Equal(t, 1, enqueuedObjects["default/rollout"]) } -func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { - ings := []*extensionsv1beta1.Ingress{ - newNginxIngress("test-stable-ingress", 80, "stable-service"), - newNginxIngress("test-stable-ingress-additional", 80, "stable-service"), - } - +func TestSkipIngressWithNoClass(t *testing.T) { + ing := newNginxIngressWithAnnotation("test-stable-ingress", 80, "stable-service") + ing.Annotations = nil rollout := &v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ Name: "rollout", @@ -216,8 +244,7 @@ func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { CanaryService: "canary-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "test-stable-ingress", - AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, + StableIngress: "test-stable-ingress", }, }, }, @@ -225,18 +252,24 @@ func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { }, } - ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, ings, rollout) + ctrl, kubeclient, enqueuedObjects := newFakeIngressController(t, ing, rollout) err := ctrl.syncIngress("default/test-stable-ingress") assert.NoError(t, err) actions := kubeclient.Actions() assert.Len(t, actions, 0) - assert.Equal(t, 1, enqueuedObjects["default/rollout"]) + assert.Len(t, enqueuedObjects, 0) } -func TestSkipIngressWithNoAnnotations(t *testing.T) { - ing := newNginxIngress("test-stable-ingress", 80, "stable-service") - ing.Annotations = nil +func TestSkipIngressWithNoClassMultiIngress(t *testing.T) { + ings := []*extensionsv1beta1.Ingress{ + newNginxIngressWithAnnotation("test-stable-ingress", 80, "stable-service"), + newNginxIngressWithAnnotation("test-stable-ingress-additional", 80, "stable-service"), + } + for _, i := range ings { + i.Annotations = nil + } + rollout := &v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ Name: "rollout", @@ -249,7 +282,8 @@ func TestSkipIngressWithNoAnnotations(t *testing.T) { CanaryService: "canary-service", TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Nginx: &v1alpha1.NginxTrafficRouting{ - StableIngress: "test-stable-ingress", + StableIngress: "test-stable-ingress", + AdditionalStableIngresses: []string{"test-stable-ingress-additional"}, }, }, }, @@ -257,7 +291,7 @@ func TestSkipIngressWithNoAnnotations(t *testing.T) { }, } - ctrl, kubeclient, enqueuedObjects := newFakeIngressController(t, ing, rollout) + ctrl, kubeclient, enqueuedObjects := newFakeIngressControllerMultiIngress(t, ings, rollout) err := ctrl.syncIngress("default/test-stable-ingress") assert.NoError(t, err) @@ -266,14 +300,11 @@ func TestSkipIngressWithNoAnnotations(t *testing.T) { assert.Len(t, enqueuedObjects, 0) } -func TestSkipIngressWithNoAnnotationsMultiIngress(t *testing.T) { +func TestSyncIngressReferencedByRolloutMultiIngress(t *testing.T) { ings := []*extensionsv1beta1.Ingress{ newNginxIngress("test-stable-ingress", 80, "stable-service"), newNginxIngress("test-stable-ingress-additional", 80, "stable-service"), } - for _, i := range ings { - i.Annotations = nil - } rollout := &v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ @@ -302,5 +333,5 @@ func TestSkipIngressWithNoAnnotationsMultiIngress(t *testing.T) { assert.NoError(t, err) actions := kubeclient.Actions() assert.Len(t, actions, 0) - assert.Len(t, enqueuedObjects, 0) + assert.Equal(t, 1, enqueuedObjects["default/rollout"]) } diff --git a/manifests/crds/analysis-run-crd.yaml b/manifests/crds/analysis-run-crd.yaml index 29b474072e..962db250b3 100644 --- a/manifests/crds/analysis-run-crd.yaml +++ b/manifests/crds/analysis-run-crd.yaml @@ -192,6 +192,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -2565,6 +2572,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/crds/analysis-template-crd.yaml b/manifests/crds/analysis-template-crd.yaml index f1a2574030..54aad4b66f 100644 --- a/manifests/crds/analysis-template-crd.yaml +++ b/manifests/crds/analysis-template-crd.yaml @@ -188,6 +188,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -2561,6 +2568,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/crds/cluster-analysis-template-crd.yaml b/manifests/crds/cluster-analysis-template-crd.yaml index 8afa62eebc..1551b9d114 100644 --- a/manifests/crds/cluster-analysis-template-crd.yaml +++ b/manifests/crds/cluster-analysis-template-crd.yaml @@ -188,6 +188,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -2561,6 +2568,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/crds/experiment-crd.yaml b/manifests/crds/experiment-crd.yaml index 0e163e4991..7af9a81d52 100644 --- a/manifests/crds/experiment-crd.yaml +++ b/manifests/crds/experiment-crd.yaml @@ -2467,6 +2467,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index a295f43046..1518a5afcb 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -593,7 +593,7 @@ spec: format: int32 type: integer type: object - setHeaderRouting: + setHeaderRoute: properties: match: items: @@ -614,6 +614,52 @@ spec: - headerValue type: object type: array + name: + type: string + type: object + setMirrorRoute: + properties: + match: + items: + properties: + headers: + additionalProperties: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + method: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + path: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + type: array + name: + type: string + percentage: + format: int32 + type: integer + required: + - name type: object setWeight: format: int32 @@ -755,6 +801,15 @@ spec: type: object type: array type: object + managedRoutes: + items: + properties: + name: + type: string + required: + - name + type: object + type: array nginx: properties: additionalIngressAnnotations: @@ -3106,6 +3161,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/dashboard-install.yaml b/manifests/dashboard-install.yaml index d31c27b37e..009f5dff73 100644 --- a/manifests/dashboard-install.yaml +++ b/manifests/dashboard-install.yaml @@ -57,6 +57,8 @@ rules: verbs: - get - update + - list + - watch - apiGroups: - apps resources: diff --git a/manifests/dashboard-install/dashboard-clusterrole.yaml b/manifests/dashboard-install/dashboard-clusterrole.yaml index dd541479a0..2499752457 100644 --- a/manifests/dashboard-install/dashboard-clusterrole.yaml +++ b/manifests/dashboard-install/dashboard-clusterrole.yaml @@ -47,6 +47,8 @@ rules: verbs: - get - update + - list + - watch - apiGroups: - apps resources: diff --git a/manifests/install.yaml b/manifests/install.yaml index d9bdc9cad1..22ea1f53a3 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -193,6 +193,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -2566,6 +2573,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -3057,6 +3067,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -5430,6 +5447,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -5807,6 +5827,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -8180,6 +8207,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -10836,6 +10866,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -11561,7 +11594,7 @@ spec: format: int32 type: integer type: object - setHeaderRouting: + setHeaderRoute: properties: match: items: @@ -11582,6 +11615,52 @@ spec: - headerValue type: object type: array + name: + type: string + type: object + setMirrorRoute: + properties: + match: + items: + properties: + headers: + additionalProperties: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + method: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + path: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + type: array + name: + type: string + percentage: + format: int32 + type: integer + required: + - name type: object setWeight: format: int32 @@ -11723,6 +11802,15 @@ spec: type: object type: array type: object + managedRoutes: + items: + properties: + name: + type: string + required: + - name + type: object + type: array nginx: properties: additionalIngressAnnotations: @@ -14074,6 +14162,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index f900efb5eb..c53eeb0c67 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -193,6 +193,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -2566,6 +2573,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -3057,6 +3067,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -5430,6 +5447,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -5807,6 +5827,13 @@ spec: query: type: string type: object + influxdb: + properties: + profile: + type: string + query: + type: string + type: object job: properties: metadata: @@ -8180,6 +8207,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -10836,6 +10866,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: @@ -11561,7 +11594,7 @@ spec: format: int32 type: integer type: object - setHeaderRouting: + setHeaderRoute: properties: match: items: @@ -11582,6 +11615,52 @@ spec: - headerValue type: object type: array + name: + type: string + type: object + setMirrorRoute: + properties: + match: + items: + properties: + headers: + additionalProperties: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + method: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + path: + properties: + exact: + type: string + prefix: + type: string + regex: + type: string + type: object + type: object + type: array + name: + type: string + percentage: + format: int32 + type: integer + required: + - name type: object setWeight: format: int32 @@ -11723,6 +11802,15 @@ spec: type: object type: array type: object + managedRoutes: + items: + properties: + name: + type: string + required: + - name + type: object + type: array nginx: properties: additionalIngressAnnotations: @@ -14074,6 +14162,9 @@ spec: maxSkew: format: int32 type: integer + minDomains: + format: int32 + type: integer topologyKey: type: string whenUnsatisfiable: diff --git a/metricproviders/influxdb/influxdb.go b/metricproviders/influxdb/influxdb.go new file mode 100644 index 0000000000..f67c8d5f94 --- /dev/null +++ b/metricproviders/influxdb/influxdb.go @@ -0,0 +1,136 @@ +package influxdb + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/argoproj/argo-rollouts/utils/defaults" + "github.com/argoproj/argo-rollouts/utils/evaluate" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" + influxapi "github.com/influxdata/influxdb-client-go/v2/api" + log "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + metricutil "github.com/argoproj/argo-rollouts/utils/metric" +) + +const ( + //ProviderType indicates the provider is InfluxDB + ProviderType = "Influxdb" + //DefaultInfluxdbTokensSecretName is the k8s secret that has InfluxDB api token, org and address + DefaultInfluxdbTokensSecretName = "influxdb" + influxdbToken = "authToken" + influxdbOrg = "org" + influxdbAddress = "address" + defaultQueryTimeout = 30 * time.Second +) + +// Provider contains all the required components to run a influxdb flux query +type Provider struct { + api influxapi.QueryAPI + logCtx log.Entry +} + +// Type indicates provider is a influxdb provider +func (p *Provider) Type() string { + return ProviderType +} + +// GetMetadata returns any additional metadata which needs to be stored & displayed as part of the metrics result. +func (p *Provider) GetMetadata(metric v1alpha1.Metric) map[string]string { + return nil +} + +// Run queries influxdb for the metric +func (p *Provider) Run(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric) v1alpha1.Measurement { + startTime := metav1.Now() + newMeasurement := v1alpha1.Measurement{ + StartedAt: &startTime, + } + ctx, cancel := context.WithTimeout(context.Background(), defaultQueryTimeout) + defer cancel() + result, err := p.api.Query(ctx, metric.Provider.Influxdb.Query) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + } + newValue, newStatus, err := p.processResponse(metric, result) + if err != nil { + return metricutil.MarkMeasurementError(newMeasurement, err) + } + newMeasurement.Value = newValue + + newMeasurement.Phase = newStatus + finishedTime := metav1.Now() + newMeasurement.FinishedAt = &finishedTime + return newMeasurement +} + +// Resume should not be used by the influxdb provider since all the work should occur in the Run method. +func (p *Provider) Resume(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + p.logCtx.Warn("Influxdb provider should not execute the Resume method") + return measurement +} + +// Terminate should not be used by the influxdb provider since all the work should occur in the Run method. +func (p *Provider) Terminate(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, measurement v1alpha1.Measurement) v1alpha1.Measurement { + p.logCtx.Warn("Influxdb provider should not execute the Terminate method") + return measurement +} + +// GarbageCollect is a no-op for the influxdb provider +func (p *Provider) GarbageCollect(run *v1alpha1.AnalysisRun, metric v1alpha1.Metric, limit int) error { + return nil +} + +func (p *Provider) processResponse(metric v1alpha1.Metric, result *influxapi.QueryTableResult) (string, v1alpha1.AnalysisPhase, error) { + var res []interface{} + if result == nil { + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("no QueryTableResult returned from flux query") + } + for result.Next() { + res = append(res, result.Record().Value()) + } + if len(res) == 0 { + return "", v1alpha1.AnalysisPhaseError, fmt.Errorf("no results returned from flux query") + } + status, err := evaluate.EvaluateResult(res, metric, p.logCtx) + if err != nil { + return "", v1alpha1.AnalysisPhaseError, err + } + return fmt.Sprint(res), status, err +} + +// NewInfluxdbProvider Creates a new Influxdb client +func NewInfluxdbProvider(api influxapi.QueryAPI, logCtx log.Entry) *Provider { + return &Provider{ + logCtx: logCtx, + api: api, + } +} + +// NewInfluxdbAPI generates a Influx API from the metric configuration +func NewInfluxdbAPI(metric v1alpha1.Metric, kubeclientset kubernetes.Interface) (influxapi.QueryAPI, error) { + profileSecret := DefaultInfluxdbTokensSecretName + if metric.Provider.Influxdb.Profile != "" { + profileSecret = metric.Provider.Influxdb.Profile + } + ns := defaults.Namespace() + secret, err := kubeclientset.CoreV1().Secrets(ns).Get(context.TODO(), profileSecret, metav1.GetOptions{}) + if err != nil { + return nil, err + } + authToken := string(secret.Data[influxdbToken]) + address := string(secret.Data[influxdbAddress]) + org := string(secret.Data[influxdbOrg]) + + if authToken != "" && address != "" && org != "" { + influxClient := influxdb2.NewClient(address, authToken) + return influxClient.QueryAPI(org), nil + } + + return nil, errors.New("authToken, org, or address not found") +} diff --git a/metricproviders/influxdb/influxdb_test.go b/metricproviders/influxdb/influxdb_test.go new file mode 100644 index 0000000000..d4f1512e54 --- /dev/null +++ b/metricproviders/influxdb/influxdb_test.go @@ -0,0 +1,249 @@ +package influxdb + +import ( + "errors" + "fmt" + "io/ioutil" + "strings" + "testing" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + influxdb2 "github.com/influxdata/influxdb-client-go/v2/api" + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + k8sfake "k8s.io/client-go/kubernetes/fake" + kubetesting "k8s.io/client-go/testing" +) + +func newAnalysisRun() *v1alpha1.AnalysisRun { + return &v1alpha1.AnalysisRun{} +} + +func TestType(t *testing.T) { + e := log.Entry{} + mock := &mockAPI{} + p := NewInfluxdbProvider(mock, e) + assert.Equal(t, ProviderType, p.Type()) +} + +func TestRunSuccessfully(t *testing.T) { + e := log.Entry{} + csvTable := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,1.0,f,test,1,adsfasdf +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,6.6,f,test,1,adsfasdf +` + reader := strings.NewReader(csvTable) + result := influxdb2.NewQueryTableResult(ioutil.NopCloser(reader)) + mock := &mockAPI{response: result} + p := NewInfluxdbProvider(mock, e) + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result[0] == 1", + FailureCondition: "result[0] != 1", + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{ + Query: "test", + }, + }, + } + measurement := p.Run(newAnalysisRun(), metric) + metricsMetadata := p.GetMetadata(metric) + assert.Nil(t, metricsMetadata) + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, `[1 6.6]`, measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, measurement.Phase) +} + +func TestRunWithTimeseries(t *testing.T) { + e := log.Entry{} + csvTable := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T10:34:08.135814545Z,10,f,test,1,adsfasdf +,,0,2020-02-17T22:19:49.747562847Z,2020-02-18T22:19:49.747562847Z,2020-02-18T22:08:44.850214724Z,20,f,test,1,adsfasdf +` + reader := strings.NewReader(csvTable) + result := influxdb2.NewQueryTableResult(ioutil.NopCloser(reader)) + mock := &mockAPI{response: result} + p := NewInfluxdbProvider(mock, e) + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result[0] == 10", + FailureCondition: "result[1] < 20", + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{ + Query: "test", + }, + }, + } + measurement := p.Run(newAnalysisRun(), metric) + metricsMetadata := p.GetMetadata(metric) + assert.Nil(t, metricsMetadata) + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, `[10 20]`, measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + assert.Equal(t, v1alpha1.AnalysisPhaseSuccessful, measurement.Phase) +} + +func TestRunWithEmptyResult(t *testing.T) { + e := log.NewEntry(log.New()) + expectedErr := fmt.Errorf("no results returned from flux query") + csvTable := `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#group,false,false,true,true,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,a,b +` + reader := strings.NewReader(csvTable) + result := influxdb2.NewQueryTableResult(ioutil.NopCloser(reader)) + mock := &mockAPI{response: result} + p := NewInfluxdbProvider(mock, *e) + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result[0] == 10", + FailureCondition: "result[0] != 10", + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{ + Query: "test", + }, + }, + } + measurement := p.Run(newAnalysisRun(), metric) + metricsMetadata := p.GetMetadata(metric) + assert.Nil(t, metricsMetadata) + assert.Equal(t, expectedErr.Error(), measurement.Message) + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, "", measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + assert.Equal(t, v1alpha1.AnalysisPhaseError, measurement.Phase) +} + +func TestRunWithEvaluationError(t *testing.T) { + e := log.WithField("", "") + mock := &mockAPI{} + p := NewInfluxdbProvider(mock, *e) + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result == 10", + FailureCondition: "result != 10", + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{ + Query: "test", + }, + }, + } + measurement := p.Run(newAnalysisRun(), metric) + metricsMetadata := p.GetMetadata(metric) + assert.Nil(t, metricsMetadata) + assert.Equal(t, "no QueryTableResult returned from flux query", measurement.Message) + assert.NotNil(t, measurement.StartedAt) + assert.Equal(t, "", measurement.Value) + assert.NotNil(t, measurement.FinishedAt) + assert.Equal(t, v1alpha1.AnalysisPhaseError, measurement.Phase) +} + +func TestResume(t *testing.T) { + e := log.WithField("", "") + mock := &mockAPI{} + p := NewInfluxdbProvider(mock, *e) + metric := v1alpha1.Metric{ + Name: "foo", + SuccessCondition: "result == 10", + FailureCondition: "result != 10", + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{ + Query: "test", + }, + }, + } + now := metav1.Now() + previousMeasurement := v1alpha1.Measurement{ + StartedAt: &now, + Phase: v1alpha1.AnalysisPhaseInconclusive, + } + measurement := p.Resume(newAnalysisRun(), metric, previousMeasurement) + assert.Equal(t, previousMeasurement, measurement) +} + +func TestTerminate(t *testing.T) { + e := log.NewEntry(log.New()) + mock := &mockAPI{} + p := NewInfluxdbProvider(mock, *e) + metric := v1alpha1.Metric{} + now := metav1.Now() + previousMeasurement := v1alpha1.Measurement{ + StartedAt: &now, + Phase: v1alpha1.AnalysisPhaseRunning, + } + measurement := p.Terminate(newAnalysisRun(), metric, previousMeasurement) + assert.Equal(t, previousMeasurement, measurement) +} + +func TestGarbageCollect(t *testing.T) { + e := log.NewEntry(log.New()) + mock := &mockAPI{} + p := NewInfluxdbProvider(mock, *e) + err := p.GarbageCollect(nil, v1alpha1.Metric{}, 0) + assert.NoError(t, err) +} + +func TestNewInfluxdbAPI(t *testing.T) { + metric := v1alpha1.Metric{ + Provider: v1alpha1.MetricProvider{ + Influxdb: &v1alpha1.InfluxdbMetric{}, + }, + } + tokenSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: DefaultInfluxdbTokensSecretName, + }, + } + fakeClient := k8sfake.NewSimpleClientset() + fakeClient.PrependReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + return true, tokenSecret, nil + }) + + t.Run("with default settings", func(t *testing.T) { + tokenSecret.Data = map[string][]byte{ + influxdbToken: []byte("ABCDEFG01234"), + influxdbOrg: []byte("test-org"), + influxdbAddress: []byte("http://localhost:8086"), + } + _, err := NewInfluxdbAPI(metric, fakeClient) + assert.Nil(t, err) + }) + + t.Run("with authToken, org, or address missing", func(t *testing.T) { + tokenSecret.Data = map[string][]byte{ + influxdbToken: []byte("ABCDEFG01234"), + } + _, err := NewInfluxdbAPI(metric, fakeClient) + assert.EqualError(t, err, "authToken, org, or address not found") + }) + + t.Run("when secretName is specified by the metric", func(t *testing.T) { + metric.Provider.Influxdb.Profile = "my-influx-token-secret" + tokenSecret.Name = "my-influx-token-secret" + tokenSecret.Data = map[string][]byte{ + influxdbToken: []byte("ABCDEFG01234"), + influxdbOrg: []byte("test-org"), + influxdbAddress: []byte("http://localhost:8086"), + } + _, err := NewInfluxdbAPI(metric, fakeClient) + assert.Nil(t, err) + }) + t.Run("when the secret is not found", func(t *testing.T) { + fakeClient.PrependReactor("get", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, errors.New("secret not found") + }) + _, err := NewInfluxdbAPI(metric, fakeClient) + assert.NotNil(t, err) + }) +} diff --git a/metricproviders/influxdb/mock_test.go b/metricproviders/influxdb/mock_test.go new file mode 100644 index 0000000000..3aff084802 --- /dev/null +++ b/metricproviders/influxdb/mock_test.go @@ -0,0 +1,32 @@ +package influxdb + +import ( + "context" + + influxapi "github.com/influxdata/influxdb-client-go/v2/api" + "github.com/influxdata/influxdb-client-go/v2/domain" +) + +type mockAPI struct { + response *influxapi.QueryTableResult + err error +} + +func (m mockAPI) Query(ctx context.Context, query string) (*influxapi.QueryTableResult, error) { + if m.err != nil { + return nil, m.err + } + return m.response, nil +} + +func (m mockAPI) QueryRaw(context.Context, string, *domain.Dialect) (string, error) { + panic("Not used") +} + +func (m mockAPI) QueryRawWithParams(ctx context.Context, query string, dialect *domain.Dialect, params interface{}) (string, error) { + panic("Not used") +} + +func (m mockAPI) QueryWithParams(ctx context.Context, query string, params interface{}) (*influxapi.QueryTableResult, error) { + panic("Not used") +} diff --git a/metricproviders/metricproviders.go b/metricproviders/metricproviders.go index c963a0c245..d8a1637acf 100644 --- a/metricproviders/metricproviders.go +++ b/metricproviders/metricproviders.go @@ -3,6 +3,8 @@ package metricproviders import ( "fmt" + "github.com/argoproj/argo-rollouts/metricproviders/influxdb" + "github.com/argoproj/argo-rollouts/metricproviders/cloudwatch" "github.com/argoproj/argo-rollouts/metricproviders/datadog" "github.com/argoproj/argo-rollouts/metricproviders/graphite" @@ -88,11 +90,17 @@ func (f *ProviderFactory) NewProvider(logCtx log.Entry, metric v1alpha1.Metric) } return graphite.NewGraphiteProvider(client, logCtx), nil case cloudwatch.ProviderType: - clinet, err := cloudwatch.NewCloudWatchAPIClient(metric) + client, err := cloudwatch.NewCloudWatchAPIClient(metric) if err != nil { return nil, err } - return cloudwatch.NewCloudWatchProvider(clinet, logCtx), nil + return cloudwatch.NewCloudWatchProvider(client, logCtx), nil + case influxdb.ProviderType: + client, err := influxdb.NewInfluxdbAPI(metric, f.KubeClient) + if err != nil { + return nil, err + } + return influxdb.NewInfluxdbProvider(client, logCtx), nil default: return nil, fmt.Errorf("no valid provider in metric '%s'", metric.Name) } @@ -115,6 +123,11 @@ func Type(metric v1alpha1.Metric) string { return newrelic.ProviderType } else if metric.Provider.CloudWatch != nil { return cloudwatch.ProviderType + } else if metric.Provider.Graphite != nil { + return graphite.ProviderType + } else if metric.Provider.Influxdb != nil { + return influxdb.ProviderType } + return "Unknown Provider" } diff --git a/metricproviders/mocks/Provider.go b/metricproviders/mocks/Provider.go index 7e483078ae..1b54e4281c 100644 --- a/metricproviders/mocks/Provider.go +++ b/metricproviders/mocks/Provider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v0.0.0-dev. DO NOT EDIT. +// Code generated by mockery v2.14.0. DO NOT EDIT. package mocks @@ -97,3 +97,18 @@ func (_m *Provider) Type() string { return r0 } + +type mockConstructorTestingTNewProvider interface { + mock.TestingT + Cleanup(func()) +} + +// NewProvider creates a new instance of Provider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewProvider(t mockConstructorTestingTNewProvider) *Provider { + mock := &Provider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/metricproviders/prometheus/mock_test.go b/metricproviders/prometheus/mock_test.go index ae833b8074..e16a42902d 100644 --- a/metricproviders/prometheus/mock_test.go +++ b/metricproviders/prometheus/mock_test.go @@ -19,7 +19,7 @@ func (m mockAPI) WalReplay(ctx context.Context) (v1.WalReplayStatus, error) { } // Query performs a query for the given time. -func (m mockAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, v1.Warnings, error) { +func (m mockAPI) Query(ctx context.Context, query string, ts time.Time, opt ...v1.Option) (model.Value, v1.Warnings, error) { if m.err != nil { return nil, m.warnings, m.err } @@ -48,7 +48,7 @@ func (m mockAPI) LabelValues(ctx context.Context, label string, matches []string panic("Not used") } -func (m mockAPI) QueryRange(ctx context.Context, query string, r v1.Range) (model.Value, v1.Warnings, error) { +func (m mockAPI) QueryRange(ctx context.Context, query string, r v1.Range, opt ...v1.Option) (model.Value, v1.Warnings, error) { panic("Not used") } diff --git a/mkdocs.yml b/mkdocs.yml index 2e32bd682d..a84ef3a807 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,6 +55,8 @@ nav: - Web: analysis/web.md - Kayenta: analysis/kayenta.md - CloudWatch: analysis/cloudwatch.md + - Graphite: analysis/graphite.md + - InfluxDB: analysis/influxdb.md - Experiments: features/experiment.md - Notifications: - Overview: features/notifications.md diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 8028a3fbc5..ac5b5a404b 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -821,9 +821,13 @@ "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetCanaryScale", "title": "SetCanaryScale defines how to scale the newRS without changing traffic weight\n+optional" }, - "setHeaderRouting": { - "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRouting", - "title": "SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service" + "setHeaderRoute": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRoute", + "title": "SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service\n+optional" + }, + "setMirrorRoute": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetMirrorRoute", + "title": "SetMirrorRoutes Mirrors traffic that matches rules to a particular destination\n+optional" } }, "description": "CanaryStep defines a step of a canary deployment." @@ -995,6 +999,14 @@ }, "title": "IstioVirtualService holds information on the virtual service the rollout needs to modify" }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MangedRoutes": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MeasurementRetention": { "type": "object", "properties": { @@ -1552,10 +1564,37 @@ "traefik": { "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.TraefikTrafficRouting", "title": "Traefik holds specific configuration to use Traefik to route traffic" + }, + "managedRoutes": { + "type": "array", + "items": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MangedRoutes" + }, + "description": "A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream\ntraffic router." } }, "title": "RolloutTrafficRouting hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing" }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch": { + "type": "object", + "properties": { + "method": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StringMatch", + "title": "Method What http methods should be mirrored\n+optional" + }, + "path": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StringMatch", + "title": "Path What url paths should be mirrored\n+optional" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StringMatch" + }, + "title": "Headers What request with matching headers should be mirrored\n+optional" + } + } + }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SMITrafficRouting": { "type": "object", "properties": { @@ -1590,9 +1629,13 @@ }, "title": "SetCanaryScale defines how to scale the newRS without changing traffic weight" }, - "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRouting": { + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRoute": { "type": "object", "properties": { + "name": { + "type": "string", + "title": "Name this is the name of the route to use for the mirroring of traffic this also needs\nto be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field" + }, "match": { "type": "array", "items": { @@ -1600,7 +1643,28 @@ } } }, - "title": "SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service" + "title": "SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service" + }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetMirrorRoute": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Name this is the name of the route to use for the mirroring of traffic this also needs\nto be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field" + }, + "match": { + "type": "array", + "items": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch" + }, + "title": "Match Contains a list of rules that if mated will mirror the traffic to the services\n+optional" + }, + "percentage": { + "type": "integer", + "format": "int32", + "title": "Services The list of services to mirror the traffic to if the method, path, headers match\nService string `json:\"service\" protobuf:\"bytes,3,opt,name=service\"`\nPercentage What percent of the traffic that matched the rules should be mirrored" + } + } }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig": { "type": "object", @@ -1766,20 +1830,20 @@ "properties": { "volumeID": { "type": "string", - "title": "Unique ID of the persistent disk resource in AWS (Amazon EBS volume).\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" + "title": "volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume).\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore" }, "fsType": { "type": "string", - "title": "Filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" + "title": "fsType is the filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" }, "partition": { "type": "integer", "format": "int32", - "title": "The partition in the volume that you want to mount.\nIf omitted, the default is to mount by volume name.\nExamples: For volume /dev/sda1, you specify the partition as \"1\".\nSimilarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).\n+optional" + "title": "partition is the partition in the volume that you want to mount.\nIf omitted, the default is to mount by volume name.\nExamples: For volume /dev/sda1, you specify the partition as \"1\".\nSimilarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).\n+optional" }, "readOnly": { "type": "boolean", - "title": "Specify \"true\" to force and set the ReadOnly property in VolumeMounts to \"true\".\nIf omitted, the default is \"false\".\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n+optional" + "title": "readOnly value true will force the readOnly setting in VolumeMounts.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n+optional" } }, "description": "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk\nmust also be in the same AWS zone as the kubelet. An AWS EBS disk\ncan only be mounted as read/write once. AWS EBS volumes support\nownership management and SELinux relabeling." @@ -1807,27 +1871,27 @@ "properties": { "diskName": { "type": "string", - "title": "The Name of the data disk in the blob storage" + "title": "diskName is the Name of the data disk in the blob storage" }, "diskURI": { "type": "string", - "title": "The URI the data disk in the blob storage" + "title": "diskURI is the URI of data disk in the blob storage" }, "cachingMode": { "type": "string", - "title": "Host Caching mode: None, Read Only, Read Write.\n+optional" + "title": "cachingMode is the Host Caching mode: None, Read Only, Read Write.\n+optional" }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" + "title": "fsType is Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" }, "readOnly": { "type": "boolean", - "title": "Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" }, "kind": { "type": "string", - "title": "Expected values Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared" + "title": "kind expected values are Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared" } }, "description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod." @@ -1837,15 +1901,15 @@ "properties": { "secretName": { "type": "string", - "title": "the name of secret that contains Azure Storage Account Name and Key" + "title": "secretName is the name of secret that contains Azure Storage Account Name and Key" }, "shareName": { "type": "string", - "title": "Share Name" + "title": "shareName is the azure share Name" }, "readOnly": { "type": "boolean", - "title": "Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" } }, "description": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod." @@ -1855,26 +1919,26 @@ "properties": { "driver": { "type": "string", - "description": "Driver is the name of the CSI driver that handles this volume.\nConsult with your admin for the correct name as registered in the cluster." + "description": "driver is the name of the CSI driver that handles this volume.\nConsult with your admin for the correct name as registered in the cluster." }, "readOnly": { "type": "boolean", - "title": "Specifies a read-only configuration for the volume.\nDefaults to false (read/write).\n+optional" + "title": "readOnly specifies a read-only configuration for the volume.\nDefaults to false (read/write).\n+optional" }, "fsType": { "type": "string", - "title": "Filesystem type to mount. Ex. \"ext4\", \"xfs\", \"ntfs\".\nIf not provided, the empty value is passed to the associated CSI driver\nwhich will determine the default filesystem to apply.\n+optional" + "title": "fsType to mount. Ex. \"ext4\", \"xfs\", \"ntfs\".\nIf not provided, the empty value is passed to the associated CSI driver\nwhich will determine the default filesystem to apply.\n+optional" }, "volumeAttributes": { "type": "object", "additionalProperties": { "type": "string" }, - "title": "VolumeAttributes stores driver-specific properties that are passed to the CSI\ndriver. Consult your driver's documentation for supported values.\n+optional" + "title": "volumeAttributes stores driver-specific properties that are passed to the CSI\ndriver. Consult your driver's documentation for supported values.\n+optional" }, "nodePublishSecretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "NodePublishSecretRef is a reference to the secret object containing\nsensitive information to pass to the CSI driver to complete the CSI\nNodePublishVolume and NodeUnpublishVolume calls.\nThis field is optional, and may be empty if no secret is required. If the\nsecret object contains more than one secret, all secret references are passed.\n+optional" + "title": "nodePublishSecretRef is a reference to the secret object containing\nsensitive information to pass to the CSI driver to complete the CSI\nNodePublishVolume and NodeUnpublishVolume calls.\nThis field is optional, and may be empty if no secret is required. If the\nsecret object contains more than one secret, all secret references are passed.\n+optional" } }, "title": "Represents a source location of a volume to mount, managed by an external CSI driver" @@ -1907,27 +1971,27 @@ "items": { "type": "string" }, - "title": "Required: Monitors is a collection of Ceph monitors\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it" + "title": "monitors is Required: Monitors is a collection of Ceph monitors\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it" }, "path": { "type": "string", - "title": "Optional: Used as the mounted root, rather than the full Ceph tree, default is /\n+optional" + "title": "path is Optional: Used as the mounted root, rather than the full Ceph tree, default is /\n+optional" }, "user": { "type": "string", - "title": "Optional: User is the rados user name, default is admin\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" + "title": "user is optional: User is the rados user name, default is admin\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" }, "secretFile": { "type": "string", - "title": "Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" + "title": "secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "Optional: SecretRef is reference to the authentication secret for User, default is empty.\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" + "title": "secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty.\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" }, "readOnly": { "type": "boolean", - "title": "Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" + "title": "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\nMore info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it\n+optional" } }, "description": "Represents a Ceph Filesystem mount that lasts the lifetime of a pod\nCephfs volumes do not support ownership management or SELinux relabeling." @@ -1937,19 +2001,19 @@ "properties": { "volumeID": { "type": "string", - "title": "volume id used to identify the volume in cinder.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md" + "title": "volumeID used to identify the volume in cinder.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md" }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" + "title": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" }, "readOnly": { "type": "boolean", - "title": "Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" + "title": "readOnly defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "Optional: points to a secret object containing parameters used to connect\nto OpenStack.\n+optional" + "title": "secretRef is optional: points to a secret object containing parameters used to connect\nto OpenStack.\n+optional" } }, "description": "Represents a cinder volume resource in Openstack.\nA Cinder volume must exist before mounting to a container.\nThe volume must also be in the same region as the kubelet.\nCinder volumes support ownership management and SELinux relabeling." @@ -1997,11 +2061,11 @@ "items": { "$ref": "#/definitions/k8s.io.api.core.v1.KeyToPath" }, - "title": "If unspecified, each key-value pair in the Data field of the referenced\nConfigMap will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the ConfigMap,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" + "title": "items if unspecified, each key-value pair in the Data field of the referenced\nConfigMap will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the ConfigMap,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" }, "optional": { "type": "boolean", - "title": "Specify whether the ConfigMap or its keys must be defined\n+optional" + "title": "optional specify whether the ConfigMap or its keys must be defined\n+optional" } }, "description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a\nprojected volume as files using the keys in the Data field as the file names,\nunless the items element is populated with specific mappings of keys to paths.\nNote that this is identical to a configmap volume source without the default\nmode." @@ -2017,16 +2081,16 @@ "items": { "$ref": "#/definitions/k8s.io.api.core.v1.KeyToPath" }, - "title": "If unspecified, each key-value pair in the Data field of the referenced\nConfigMap will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the ConfigMap,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" + "title": "items if unspecified, each key-value pair in the Data field of the referenced\nConfigMap will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the ConfigMap,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" }, "defaultMode": { "type": "integer", "format": "int32", - "title": "Optional: mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nDefaults to 0644.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" + "title": "defaultMode is optional: mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nDefaults to 0644.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" }, "optional": { "type": "boolean", - "title": "Specify whether the ConfigMap or its keys must be defined\n+optional" + "title": "optional specify whether the ConfigMap or its keys must be defined\n+optional" } }, "description": "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a\nvolume as files using the keys in the Data field as the file names, unless\nthe items element is populated with specific mappings of keys to paths.\nConfigMap volumes support ownership management and SELinux relabeling." @@ -2040,21 +2104,21 @@ }, "image": { "type": "string", - "title": "Docker image name.\nMore info: https://kubernetes.io/docs/concepts/containers/images\nThis field is optional to allow higher level config management to default or override\ncontainer images in workload controllers like Deployments and StatefulSets.\n+optional" + "title": "Container image name.\nMore info: https://kubernetes.io/docs/concepts/containers/images\nThis field is optional to allow higher level config management to default or override\ncontainer images in workload controllers like Deployments and StatefulSets.\n+optional" }, "command": { "type": "array", "items": { "type": "string" }, - "title": "Entrypoint array. Not executed within a shell.\nThe docker image's ENTRYPOINT is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" + "title": "Entrypoint array. Not executed within a shell.\nThe container image's ENTRYPOINT is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" }, "args": { "type": "array", "items": { "type": "string" }, - "title": "Arguments to the entrypoint.\nThe docker image's CMD is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" + "title": "Arguments to the entrypoint.\nThe container image's CMD is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" }, "workingDir": { "type": "string", @@ -2233,11 +2297,11 @@ "properties": { "medium": { "type": "string", - "title": "What type of storage medium should back this directory.\nThe default is \"\" which means to use the node's default medium.\nMust be an empty string (default) or Memory.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir\n+optional" + "title": "medium represents what type of storage medium should back this directory.\nThe default is \"\" which means to use the node's default medium.\nMust be an empty string (default) or Memory.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir\n+optional" }, "sizeLimit": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.api.resource.Quantity", - "title": "Total amount of local storage required for this EmptyDir volume.\nThe size limit is also applicable for memory medium.\nThe maximum usage on memory medium EmptyDir would be the minimum value between\nthe SizeLimit specified here and the sum of memory limits of all containers in a pod.\nThe default is nil which means that the limit is undefined.\nMore info: http://kubernetes.io/docs/user-guide/volumes#emptydir\n+optional" + "title": "sizeLimit is the total amount of local storage required for this EmptyDir volume.\nThe size limit is also applicable for memory medium.\nThe maximum usage on memory medium EmptyDir would be the minimum value between\nthe SizeLimit specified here and the sum of memory limits of all containers in a pod.\nThe default is nil which means that the limit is undefined.\nMore info: http://kubernetes.io/docs/user-guide/volumes#emptydir\n+optional" } }, "description": "Represents an empty directory for a pod.\nEmpty directory volumes support ownership management and SELinux relabeling." @@ -2323,21 +2387,21 @@ }, "image": { "type": "string", - "title": "Docker image name.\nMore info: https://kubernetes.io/docs/concepts/containers/images" + "title": "Container image name.\nMore info: https://kubernetes.io/docs/concepts/containers/images" }, "command": { "type": "array", "items": { "type": "string" }, - "title": "Entrypoint array. Not executed within a shell.\nThe docker image's ENTRYPOINT is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" + "title": "Entrypoint array. Not executed within a shell.\nThe image's ENTRYPOINT is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" }, "args": { "type": "array", "items": { "type": "string" }, - "title": "Arguments to the entrypoint.\nThe docker image's CMD is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" + "title": "Arguments to the entrypoint.\nThe image's CMD is used if this is not provided.\nVariable references $(VAR_NAME) are expanded using the container's environment. If a variable\ncannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced\nto a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will\nproduce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless\nof whether the variable exists or not. Cannot be updated.\nMore info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell\n+optional" }, "workingDir": { "type": "string", @@ -2460,27 +2524,27 @@ "items": { "type": "string" }, - "title": "Optional: FC target worldwide names (WWNs)\n+optional" + "title": "targetWWNs is Optional: FC target worldwide names (WWNs)\n+optional" }, "lun": { "type": "integer", "format": "int32", - "title": "Optional: FC target lun number\n+optional" + "title": "lun is Optional: FC target lun number\n+optional" }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" + "title": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" }, "readOnly": { "type": "boolean", - "title": "Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" }, "wwids": { "type": "array", "items": { "type": "string" }, - "title": "Optional: FC volume world wide identifiers (wwids)\nEither wwids or combination of targetWWNs and lun must be set, but not both simultaneously.\n+optional" + "title": "wwids Optional: FC volume world wide identifiers (wwids)\nEither wwids or combination of targetWWNs and lun must be set, but not both simultaneously.\n+optional" } }, "description": "Represents a Fibre Channel volume.\nFibre Channel volumes can only be mounted as read/write once.\nFibre Channel volumes support ownership management and SELinux relabeling." @@ -2490,26 +2554,26 @@ "properties": { "driver": { "type": "string", - "description": "Driver is the name of the driver to use for this volume." + "description": "driver is the name of the driver to use for this volume." }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.\n+optional" + "title": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "Optional: SecretRef is reference to the secret object containing\nsensitive information to pass to the plugin scripts. This may be\nempty if no secret object is specified. If the secret object\ncontains more than one secret, all secrets are passed to the plugin\nscripts.\n+optional" + "title": "secretRef is Optional: secretRef is reference to the secret object containing\nsensitive information to pass to the plugin scripts. This may be\nempty if no secret object is specified. If the secret object\ncontains more than one secret, all secrets are passed to the plugin\nscripts.\n+optional" }, "readOnly": { "type": "boolean", - "title": "Optional: Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly is Optional: defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" }, "options": { "type": "object", "additionalProperties": { "type": "string" }, - "title": "Optional: Extra command options if any.\n+optional" + "title": "options is Optional: this field holds extra command options if any.\n+optional" } }, "description": "FlexVolume represents a generic volume resource that is\nprovisioned/attached using an exec based plugin." @@ -2519,11 +2583,11 @@ "properties": { "datasetName": { "type": "string", - "title": "Name of the dataset stored as metadata -\u003e name on the dataset for Flocker\nshould be considered as deprecated\n+optional" + "title": "datasetName is Name of the dataset stored as metadata -\u003e name on the dataset for Flocker\nshould be considered as deprecated\n+optional" }, "datasetUUID": { "type": "string", - "title": "UUID of the dataset. This is unique identifier of a Flocker dataset\n+optional" + "title": "datasetUUID is the UUID of the dataset. This is unique identifier of a Flocker dataset\n+optional" } }, "description": "Represents a Flocker volume mounted by the Flocker agent.\nOne and only one of datasetName and datasetUUID should be set.\nFlocker volumes do not support ownership management or SELinux relabeling." @@ -2533,20 +2597,20 @@ "properties": { "pdName": { "type": "string", - "title": "Unique name of the PD resource in GCE. Used to identify the disk in GCE.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" + "title": "pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk" }, "fsType": { "type": "string", - "title": "Filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" + "title": "fsType is filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" }, "partition": { "type": "integer", "format": "int32", - "title": "The partition in the volume that you want to mount.\nIf omitted, the default is to mount by volume name.\nExamples: For volume /dev/sda1, you specify the partition as \"1\".\nSimilarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" + "title": "partition is the partition in the volume that you want to mount.\nIf omitted, the default is to mount by volume name.\nExamples: For volume /dev/sda1, you specify the partition as \"1\".\nSimilarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" + "title": "readOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" } }, "description": "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must\nalso be in the same GCE project and zone as the kubelet. A GCE PD\ncan only be mounted as read/write once or read-only many times. GCE\nPDs support ownership management and SELinux relabeling." @@ -2570,15 +2634,15 @@ "properties": { "repository": { "type": "string", - "title": "Repository URL" + "title": "repository is the URL" }, "revision": { "type": "string", - "title": "Commit hash for the specified revision.\n+optional" + "title": "revision is the commit hash for the specified revision.\n+optional" }, "directory": { "type": "string", - "title": "Target directory name.\nMust not contain or start with '..'. If '.' is supplied, the volume directory will be the\ngit repository. Otherwise, if specified, the volume will contain the git repository in\nthe subdirectory with the given name.\n+optional" + "title": "directory is the target directory name.\nMust not contain or start with '..'. If '.' is supplied, the volume directory will be the\ngit repository. Otherwise, if specified, the volume will contain the git repository in\nthe subdirectory with the given name.\n+optional" } }, "description": "Represents a volume that is populated with the contents of a git repository.\nGit repo volumes do not support ownership management.\nGit repo volumes support SELinux relabeling.\n\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an\nEmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir\ninto the Pod's container." @@ -2588,15 +2652,15 @@ "properties": { "endpoints": { "type": "string", - "title": "EndpointsName is the endpoint name that details Glusterfs topology.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod" + "title": "endpoints is the endpoint name that details Glusterfs topology.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod" }, "path": { "type": "string", - "title": "Path is the Glusterfs volume path.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod" + "title": "path is the Glusterfs volume path.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod" }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force the Glusterfs volume to be mounted with read-only permissions.\nDefaults to false.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod\n+optional" + "title": "readOnly here will force the Glusterfs volume to be mounted with read-only permissions.\nDefaults to false.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod\n+optional" } }, "description": "Represents a Glusterfs mount that lasts the lifetime of a pod.\nGlusterfs volumes do not support ownership management or SELinux relabeling." @@ -2666,11 +2730,11 @@ "properties": { "path": { "type": "string", - "title": "Path of the directory on the host.\nIf the path is a symlink, it will follow the link to the real path.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" + "title": "path of the directory on the host.\nIf the path is a symlink, it will follow the link to the real path.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath" }, "type": { "type": "string", - "title": "Type for HostPath Volume\nDefaults to \"\"\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n+optional" + "title": "type for HostPath Volume\nDefaults to \"\"\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n+optional" } }, "description": "Represents a host path mapped into a pod.\nHost path volumes do not support ownership management or SELinux relabeling." @@ -2680,51 +2744,51 @@ "properties": { "targetPortal": { "type": "string", - "description": "iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port\nis other than default (typically TCP ports 860 and 3260)." + "description": "targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port\nis other than default (typically TCP ports 860 and 3260)." }, "iqn": { "type": "string", - "description": "Target iSCSI Qualified Name." + "description": "iqn is the target iSCSI Qualified Name." }, "lun": { "type": "integer", "format": "int32", - "description": "iSCSI Target Lun number." + "description": "lun represents iSCSI Target Lun number." }, "iscsiInterface": { "type": "string", - "title": "iSCSI Interface Name that uses an iSCSI transport.\nDefaults to 'default' (tcp).\n+optional" + "title": "iscsiInterface is the interface Name that uses an iSCSI transport.\nDefaults to 'default' (tcp).\n+optional" }, "fsType": { "type": "string", - "title": "Filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" + "title": "fsType is the filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\n+optional" + "title": "readOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\n+optional" }, "portals": { "type": "array", "items": { "type": "string" }, - "title": "iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port\nis other than default (typically TCP ports 860 and 3260).\n+optional" + "title": "portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port\nis other than default (typically TCP ports 860 and 3260).\n+optional" }, "chapAuthDiscovery": { "type": "boolean", - "title": "whether support iSCSI Discovery CHAP authentication\n+optional" + "title": "chapAuthDiscovery defines whether support iSCSI Discovery CHAP authentication\n+optional" }, "chapAuthSession": { "type": "boolean", - "title": "whether support iSCSI Session CHAP authentication\n+optional" + "title": "chapAuthSession defines whether support iSCSI Session CHAP authentication\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "CHAP Secret for iSCSI target and initiator authentication\n+optional" + "title": "secretRef is the CHAP Secret for iSCSI target and initiator authentication\n+optional" }, "initiatorName": { "type": "string", - "title": "Custom iSCSI Initiator Name.\nIf initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface\n\u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.\n+optional" + "title": "initiatorName is the custom iSCSI Initiator Name.\nIf initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface\n\u003ctarget portal\u003e:\u003cvolume name\u003e will be created for the connection.\n+optional" } }, "description": "Represents an ISCSI disk.\nISCSI volumes can only be mounted as read/write once.\nISCSI volumes support ownership management and SELinux relabeling." @@ -2734,16 +2798,16 @@ "properties": { "key": { "type": "string", - "description": "The key to project." + "description": "key is the key to project." }, "path": { "type": "string", - "description": "The relative path of the file to map the key to.\nMay not be an absolute path.\nMay not contain the path element '..'.\nMay not start with the string '..'." + "description": "path is the relative path of the file to map the key to.\nMay not be an absolute path.\nMay not contain the path element '..'.\nMay not start with the string '..'." }, "mode": { "type": "integer", "format": "int32", - "title": "Optional: mode bits used to set permissions on this file.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nIf not specified, the volume defaultMode will be used.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" + "title": "mode is Optional: mode bits used to set permissions on this file.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nIf not specified, the volume defaultMode will be used.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" } }, "description": "Maps a string key to a path within a volume." @@ -2795,15 +2859,15 @@ "properties": { "server": { "type": "string", - "title": "Server is the hostname or IP address of the NFS server.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" + "title": "server is the hostname or IP address of the NFS server.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" }, "path": { "type": "string", - "title": "Path that is exported by the NFS server.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" + "title": "path that is exported by the NFS server.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs" }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force\nthe NFS export to be mounted with read-only permissions.\nDefaults to false.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs\n+optional" + "title": "readOnly here will force the NFS export to be mounted with read-only permissions.\nDefaults to false.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs\n+optional" } }, "description": "Represents an NFS mount that lasts the lifetime of a pod.\nNFS volumes do not support ownership management or SELinux relabeling." @@ -2901,23 +2965,23 @@ "items": { "type": "string" }, - "title": "AccessModes contains the desired access modes the volume should have.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1\n+optional" + "title": "accessModes contains the desired access modes the volume should have.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1\n+optional" }, "selector": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector", - "title": "A label query over volumes to consider for binding.\n+optional" + "title": "selector is a label query over volumes to consider for binding.\n+optional" }, "resources": { "$ref": "#/definitions/k8s.io.api.core.v1.ResourceRequirements", - "title": "Resources represents the minimum resources the volume should have.\nIf RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements\nthat are lower than previous value but must still be higher than capacity recorded in the\nstatus field of the claim.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources\n+optional" + "title": "resources represents the minimum resources the volume should have.\nIf RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements\nthat are lower than previous value but must still be higher than capacity recorded in the\nstatus field of the claim.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources\n+optional" }, "volumeName": { "type": "string", - "title": "VolumeName is the binding reference to the PersistentVolume backing this claim.\n+optional" + "title": "volumeName is the binding reference to the PersistentVolume backing this claim.\n+optional" }, "storageClassName": { "type": "string", - "title": "Name of the StorageClass required by the claim.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1\n+optional" + "title": "storageClassName is the name of the StorageClass required by the claim.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1\n+optional" }, "volumeMode": { "type": "string", @@ -2925,11 +2989,11 @@ }, "dataSource": { "$ref": "#/definitions/k8s.io.api.core.v1.TypedLocalObjectReference", - "title": "This field can be used to specify either:\n* An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n* An existing PVC (PersistentVolumeClaim)\nIf the provisioner or an external controller can support the specified data source,\nit will create a new volume based on the contents of the specified data source.\nIf the AnyVolumeDataSource feature gate is enabled, this field will always have\nthe same contents as the DataSourceRef field.\n+optional" + "title": "dataSource field can be used to specify either:\n* An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot)\n* An existing PVC (PersistentVolumeClaim)\nIf the provisioner or an external controller can support the specified data source,\nit will create a new volume based on the contents of the specified data source.\nIf the AnyVolumeDataSource feature gate is enabled, this field will always have\nthe same contents as the DataSourceRef field.\n+optional" }, "dataSourceRef": { "$ref": "#/definitions/k8s.io.api.core.v1.TypedLocalObjectReference", - "title": "Specifies the object from which to populate the volume with data, if a non-empty\nvolume is desired. This may be any local object from a non-empty API group (non\ncore object) or a PersistentVolumeClaim object.\nWhen this field is specified, volume binding will only succeed if the type of\nthe specified object matches some installed volume populator or dynamic\nprovisioner.\nThis field will replace the functionality of the DataSource field and as such\nif both fields are non-empty, they must have the same value. For backwards\ncompatibility, both fields (DataSource and DataSourceRef) will be set to the same\nvalue automatically if one of them is empty and the other is non-empty.\nThere are two important differences between DataSource and DataSourceRef:\n* While DataSource only allows two specific types of objects, DataSourceRef\n allows any non-core object, as well as PersistentVolumeClaim objects.\n* While DataSource ignores disallowed values (dropping them), DataSourceRef\n preserves all values, and generates an error if a disallowed value is\n specified.\n(Alpha) Using this field requires the AnyVolumeDataSource feature gate to be enabled.\n+optional" + "title": "dataSourceRef specifies the object from which to populate the volume with data, if a non-empty\nvolume is desired. This may be any local object from a non-empty API group (non\ncore object) or a PersistentVolumeClaim object.\nWhen this field is specified, volume binding will only succeed if the type of\nthe specified object matches some installed volume populator or dynamic\nprovisioner.\nThis field will replace the functionality of the DataSource field and as such\nif both fields are non-empty, they must have the same value. For backwards\ncompatibility, both fields (DataSource and DataSourceRef) will be set to the same\nvalue automatically if one of them is empty and the other is non-empty.\nThere are two important differences between DataSource and DataSourceRef:\n* While DataSource only allows two specific types of objects, DataSourceRef\n allows any non-core object, as well as PersistentVolumeClaim objects.\n* While DataSource ignores disallowed values (dropping them), DataSourceRef\n preserves all values, and generates an error if a disallowed value is\n specified.\n(Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled.\n+optional" } }, "title": "PersistentVolumeClaimSpec describes the common attributes of storage devices\nand allows a Source for provider-specific attributes" @@ -2953,11 +3017,11 @@ "properties": { "claimName": { "type": "string", - "title": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" + "title": "claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims" }, "readOnly": { "type": "boolean", - "title": "Will force the ReadOnly setting in VolumeMounts.\nDefault false.\n+optional" + "title": "readOnly Will force the ReadOnly setting in VolumeMounts.\nDefault false.\n+optional" } }, "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.\nThis volume finds the bound PV and mounts that volume for the pod. A\nPersistentVolumeClaimVolumeSource is, essentially, a wrapper around another\ntype of volume that is owned by someone else (the system)." @@ -2967,11 +3031,11 @@ "properties": { "pdID": { "type": "string", - "title": "ID that identifies Photon Controller persistent disk" + "title": "pdID is the ID that identifies Photon Controller persistent disk" }, "fsType": { "type": "string", - "description": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." + "description": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified." } }, "description": "Represents a Photon Controller persistent disk resource." @@ -3008,7 +3072,7 @@ "items": { "type": "string" }, - "title": "namespaces specifies a static list of namespace names that the term applies to.\nThe term is applied to the union of the namespaces listed in this field\nand the ones selected by namespaceSelector.\nnull or empty namespaces list and null namespaceSelector means \"this pod's namespace\"\n+optional" + "title": "namespaces specifies a static list of namespace names that the term applies to.\nThe term is applied to the union of the namespaces listed in this field\nand the ones selected by namespaceSelector.\nnull or empty namespaces list and null namespaceSelector means \"this pod's namespace\".\n+optional" }, "topologyKey": { "type": "string", @@ -3016,7 +3080,7 @@ }, "namespaceSelector": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector", - "title": "A label query over the set of namespaces that the term applies to.\nThe term is applied to the union of the namespaces selected by this field\nand the ones listed in the namespaces field.\nnull selector and null or empty namespaces list means \"this pod's namespace\".\nAn empty selector ({}) matches all namespaces.\nThis field is beta-level and is only honored when PodAffinityNamespaceSelector feature is enabled.\n+optional" + "title": "A label query over the set of namespaces that the term applies to.\nThe term is applied to the union of the namespaces selected by this field\nand the ones listed in the namespaces field.\nnull selector and null or empty namespaces list means \"this pod's namespace\".\nAn empty selector ({}) matches all namespaces.\n+optional" } }, "title": "Defines a set of pods (namely those matching the labelSelector\nrelative to the given namespace(s)) that this pod should be\nco-located (affinity) or not co-located (anti-affinity) with,\nwhere co-located is defined as running on a node whose value of\nthe label with key \u003ctopologyKey\u003e matches that of any node on which\na pod of the set of pods is running" @@ -3256,7 +3320,7 @@ "items": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference" }, - "title": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec.\nIf specified, these secrets will be passed to individual puller implementations for them to use. For example,\nin the case of docker, only DockerConfig type secrets are honored.\nMore info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod\n+optional\n+patchMergeKey=name\n+patchStrategy=merge" + "title": "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec.\nIf specified, these secrets will be passed to individual puller implementations for them to use.\nMore info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod\n+optional\n+patchMergeKey=name\n+patchStrategy=merge" }, "hostname": { "type": "string", @@ -3310,7 +3374,7 @@ }, "runtimeClassName": { "type": "string", - "title": "RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used\nto run this pod. If no RuntimeClass resource matches the named class, the pod will not be run.\nIf unset or empty, the \"legacy\" RuntimeClass will be used, which is an implicit class with an\nempty definition that uses the default runtime handler.\nMore info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class\nThis is a beta feature as of Kubernetes v1.14.\n+optional" + "title": "RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used\nto run this pod. If no RuntimeClass resource matches the named class, the pod will not be run.\nIf unset or empty, the \"legacy\" RuntimeClass will be used, which is an implicit class with an\nempty definition that uses the default runtime handler.\nMore info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class\n+optional" }, "enableServiceLinks": { "type": "boolean", @@ -3318,14 +3382,14 @@ }, "preemptionPolicy": { "type": "string", - "title": "PreemptionPolicy is the Policy for preempting pods with lower priority.\nOne of Never, PreemptLowerPriority.\nDefaults to PreemptLowerPriority if unset.\nThis field is beta-level, gated by the NonPreemptingPriority feature-gate.\n+optional" + "title": "PreemptionPolicy is the Policy for preempting pods with lower priority.\nOne of Never, PreemptLowerPriority.\nDefaults to PreemptLowerPriority if unset.\n+optional" }, "overhead": { "type": "object", "additionalProperties": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.api.resource.Quantity" }, - "title": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass.\nThis field will be autopopulated at admission time by the RuntimeClass admission controller. If\nthe RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests.\nThe RuntimeClass admission controller will reject Pod create requests which have the overhead already\nset. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value\ndefined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero.\nMore info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md\nThis field is beta-level as of Kubernetes v1.18, and is only honored by servers that enable the PodOverhead feature.\n+optional" + "title": "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass.\nThis field will be autopopulated at admission time by the RuntimeClass admission controller. If\nthe RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests.\nThe RuntimeClass admission controller will reject Pod create requests which have the overhead already\nset. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value\ndefined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero.\nMore info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md\n+optional" }, "topologySpreadConstraints": { "type": "array", @@ -3340,7 +3404,7 @@ }, "os": { "$ref": "#/definitions/k8s.io.api.core.v1.PodOS", - "description": "Specifies the OS of the containers in the pod.\nSome pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset:\n-securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset:\n- spec.hostPID\n- spec.hostIPC\n- spec.securityContext.seLinuxOptions\n- spec.securityContext.seccompProfile\n- spec.securityContext.fsGroup\n- spec.securityContext.fsGroupChangePolicy\n- spec.securityContext.sysctls\n- spec.shareProcessNamespace\n- spec.securityContext.runAsUser\n- spec.securityContext.runAsGroup\n- spec.securityContext.supplementalGroups\n- spec.containers[*].securityContext.seLinuxOptions\n- spec.containers[*].securityContext.seccompProfile\n- spec.containers[*].securityContext.capabilities\n- spec.containers[*].securityContext.readOnlyRootFilesystem\n- spec.containers[*].securityContext.privileged\n- spec.containers[*].securityContext.allowPrivilegeEscalation\n- spec.containers[*].securityContext.procMount\n- spec.containers[*].securityContext.runAsUser\n- spec.containers[*].securityContext.runAsGroup\n+optional\nThis is an alpha field and requires the IdentifyPodOS feature" + "description": "Specifies the OS of the containers in the pod.\nSome pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset:\n-securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset:\n- spec.hostPID\n- spec.hostIPC\n- spec.securityContext.seLinuxOptions\n- spec.securityContext.seccompProfile\n- spec.securityContext.fsGroup\n- spec.securityContext.fsGroupChangePolicy\n- spec.securityContext.sysctls\n- spec.shareProcessNamespace\n- spec.securityContext.runAsUser\n- spec.securityContext.runAsGroup\n- spec.securityContext.supplementalGroups\n- spec.containers[*].securityContext.seLinuxOptions\n- spec.containers[*].securityContext.seccompProfile\n- spec.containers[*].securityContext.capabilities\n- spec.containers[*].securityContext.readOnlyRootFilesystem\n- spec.containers[*].securityContext.privileged\n- spec.containers[*].securityContext.allowPrivilegeEscalation\n- spec.containers[*].securityContext.procMount\n- spec.containers[*].securityContext.runAsUser\n- spec.containers[*].securityContext.runAsGroup\n+optional\nThis is a beta field and requires the IdentifyPodOS feature" } }, "description": "PodSpec is a description of a pod." @@ -3364,15 +3428,15 @@ "properties": { "volumeID": { "type": "string", - "title": "VolumeID uniquely identifies a Portworx volume" + "title": "volumeID uniquely identifies a Portworx volume" }, "fsType": { "type": "string", - "description": "FSType represents the filesystem type to mount\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified." + "description": "fSType represents the filesystem type to mount\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified." }, "readOnly": { "type": "boolean", - "title": "Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" } }, "description": "PortworxVolumeSource represents a Portworx volume resource." @@ -3449,7 +3513,7 @@ }, "grpc": { "$ref": "#/definitions/k8s.io.api.core.v1.GRPCAction", - "title": "GRPC specifies an action involving a GRPC port.\nThis is an alpha field and requires enabling GRPCContainerProbe feature gate.\n+featureGate=GRPCContainerProbe\n+optional" + "title": "GRPC specifies an action involving a GRPC port.\nThis is a beta field and requires enabling GRPCContainerProbe feature gate.\n+featureGate=GRPCContainerProbe\n+optional" } }, "description": "ProbeHandler defines a specific action that should be taken in a probe.\nOne and only one of the fields must be specified." @@ -3462,12 +3526,12 @@ "items": { "$ref": "#/definitions/k8s.io.api.core.v1.VolumeProjection" }, - "title": "list of volume projections\n+optional" + "title": "sources is the list of volume projections\n+optional" }, "defaultMode": { "type": "integer", "format": "int32", - "title": "Mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" + "title": "defaultMode are the mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values for mode bits.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" } }, "title": "Represents a projected volume source" @@ -3477,27 +3541,27 @@ "properties": { "registry": { "type": "string", - "title": "Registry represents a single or multiple Quobyte Registry services\nspecified as a string as host:port pair (multiple entries are separated with commas)\nwhich acts as the central registry for volumes" + "title": "registry represents a single or multiple Quobyte Registry services\nspecified as a string as host:port pair (multiple entries are separated with commas)\nwhich acts as the central registry for volumes" }, "volume": { "type": "string", - "description": "Volume is a string that references an already created Quobyte volume by name." + "description": "volume is a string that references an already created Quobyte volume by name." }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force the Quobyte volume to be mounted with read-only permissions.\nDefaults to false.\n+optional" + "title": "readOnly here will force the Quobyte volume to be mounted with read-only permissions.\nDefaults to false.\n+optional" }, "user": { "type": "string", - "title": "User to map volume access to\nDefaults to serivceaccount user\n+optional" + "title": "user to map volume access to\nDefaults to serivceaccount user\n+optional" }, "group": { "type": "string", - "title": "Group to map volume access to\nDefault is no group\n+optional" + "title": "group to map volume access to\nDefault is no group\n+optional" }, "tenant": { "type": "string", - "title": "Tenant owning the given Quobyte volume in the Backend\nUsed with dynamically provisioned Quobyte volumes, value is set by the plugin\n+optional" + "title": "tenant owning the given Quobyte volume in the Backend\nUsed with dynamically provisioned Quobyte volumes, value is set by the plugin\n+optional" } }, "description": "Represents a Quobyte mount that lasts the lifetime of a pod.\nQuobyte volumes do not support ownership management or SELinux relabeling." @@ -3510,35 +3574,35 @@ "items": { "type": "string" }, - "title": "A collection of Ceph monitors.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" + "title": "monitors is a collection of Ceph monitors.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" }, "image": { "type": "string", - "title": "The rados image name.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" + "title": "image is the rados image name.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it" }, "fsType": { "type": "string", - "title": "Filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#rbd\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" + "title": "fsType is the filesystem type of the volume that you want to mount.\nTip: Ensure that the filesystem type is supported by the host operating system.\nExamples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#rbd\nTODO: how do we prevent errors in the filesystem from compromising the machine\n+optional" }, "pool": { "type": "string", - "title": "The rados pool name.\nDefault is rbd.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" + "title": "pool is the rados pool name.\nDefault is rbd.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" }, "user": { "type": "string", - "title": "The rados user name.\nDefault is admin.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" + "title": "user is the rados user name.\nDefault is admin.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" }, "keyring": { "type": "string", - "title": "Keyring is the path to key ring for RBDUser.\nDefault is /etc/ceph/keyring.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" + "title": "keyring is the path to key ring for RBDUser.\nDefault is /etc/ceph/keyring.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "SecretRef is name of the authentication secret for RBDUser. If provided\noverrides keyring.\nDefault is nil.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" + "title": "secretRef is name of the authentication secret for RBDUser. If provided\noverrides keyring.\nDefault is nil.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" }, "readOnly": { "type": "boolean", - "title": "ReadOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" + "title": "readOnly here will force the ReadOnly setting in VolumeMounts.\nDefaults to false.\nMore info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it\n+optional" } }, "description": "Represents a Rados Block Device mount that lasts the lifetime of a pod.\nRBD volumes support ownership management and SELinux relabeling." @@ -3608,43 +3672,43 @@ "properties": { "gateway": { "type": "string", - "description": "The host address of the ScaleIO API Gateway." + "description": "gateway is the host address of the ScaleIO API Gateway." }, "system": { "type": "string", - "description": "The name of the storage system as configured in ScaleIO." + "description": "system is the name of the storage system as configured in ScaleIO." }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "description": "SecretRef references to the secret for ScaleIO user and other\nsensitive information. If this is not provided, Login operation will fail." + "description": "secretRef references to the secret for ScaleIO user and other\nsensitive information. If this is not provided, Login operation will fail." }, "sslEnabled": { "type": "boolean", - "title": "Flag to enable/disable SSL communication with Gateway, default false\n+optional" + "title": "sslEnabled Flag enable/disable SSL communication with Gateway, default false\n+optional" }, "protectionDomain": { "type": "string", - "title": "The name of the ScaleIO Protection Domain for the configured storage.\n+optional" + "title": "protectionDomain is the name of the ScaleIO Protection Domain for the configured storage.\n+optional" }, "storagePool": { "type": "string", - "title": "The ScaleIO Storage Pool associated with the protection domain.\n+optional" + "title": "storagePool is the ScaleIO Storage Pool associated with the protection domain.\n+optional" }, "storageMode": { "type": "string", - "title": "Indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.\nDefault is ThinProvisioned.\n+optional" + "title": "storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned.\nDefault is ThinProvisioned.\n+optional" }, "volumeName": { "type": "string", - "description": "The name of a volume already created in the ScaleIO system\nthat is associated with this volume source." + "description": "volumeName is the name of a volume already created in the ScaleIO system\nthat is associated with this volume source." }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\".\nDefault is \"xfs\".\n+optional" + "title": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\".\nDefault is \"xfs\".\n+optional" }, "readOnly": { "type": "boolean", - "title": "Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" } }, "title": "ScaleIOVolumeSource represents a persistent ScaleIO volume" @@ -3707,11 +3771,11 @@ "items": { "$ref": "#/definitions/k8s.io.api.core.v1.KeyToPath" }, - "title": "If unspecified, each key-value pair in the Data field of the referenced\nSecret will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the Secret,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" + "title": "items if unspecified, each key-value pair in the Data field of the referenced\nSecret will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the Secret,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" }, "optional": { "type": "boolean", - "title": "Specify whether the Secret or its key must be defined\n+optional" + "title": "optional field specify whether the Secret or its key must be defined\n+optional" } }, "description": "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a\nprojected volume as files using the keys in the Data field as the file names.\nNote that this is identical to a secret volume source without the default\nmode." @@ -3721,23 +3785,23 @@ "properties": { "secretName": { "type": "string", - "title": "Name of the secret in the pod's namespace to use.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#secret\n+optional" + "title": "secretName is the name of the secret in the pod's namespace to use.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#secret\n+optional" }, "items": { "type": "array", "items": { "$ref": "#/definitions/k8s.io.api.core.v1.KeyToPath" }, - "title": "If unspecified, each key-value pair in the Data field of the referenced\nSecret will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the Secret,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" + "title": "items If unspecified, each key-value pair in the Data field of the referenced\nSecret will be projected into the volume as a file whose name is the\nkey and content is the value. If specified, the listed keys will be\nprojected into the specified paths, and unlisted keys will not be\npresent. If a key is specified which is not present in the Secret,\nthe volume setup will error unless it is marked optional. Paths must be\nrelative and may not contain the '..' path or start with '..'.\n+optional" }, "defaultMode": { "type": "integer", "format": "int32", - "title": "Optional: mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values\nfor mode bits. Defaults to 0644.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" + "title": "defaultMode is Optional: mode bits used to set permissions on created files by default.\nMust be an octal value between 0000 and 0777 or a decimal value between 0 and 511.\nYAML accepts both octal and decimal values, JSON requires decimal values\nfor mode bits. Defaults to 0644.\nDirectories within the path are not affected by this setting.\nThis might be in conflict with other options that affect the file\nmode, like fsGroup, and the result can be other mode bits set.\n+optional" }, "optional": { "type": "boolean", - "title": "Specify whether the Secret or its keys must be defined\n+optional" + "title": "optional field specify whether the Secret or its keys must be defined\n+optional" } }, "description": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume\nas files using the keys in the Data field as the file names.\nSecret volumes support ownership management and SELinux relabeling." @@ -3799,16 +3863,16 @@ "properties": { "audience": { "type": "string", - "title": "Audience is the intended audience of the token. A recipient of a token\nmust identify itself with an identifier specified in the audience of the\ntoken, and otherwise should reject the token. The audience defaults to the\nidentifier of the apiserver.\n+optional" + "title": "audience is the intended audience of the token. A recipient of a token\nmust identify itself with an identifier specified in the audience of the\ntoken, and otherwise should reject the token. The audience defaults to the\nidentifier of the apiserver.\n+optional" }, "expirationSeconds": { "type": "string", "format": "int64", - "title": "ExpirationSeconds is the requested duration of validity of the service\naccount token. As the token approaches expiration, the kubelet volume\nplugin will proactively rotate the service account token. The kubelet will\nstart trying to rotate the token if the token is older than 80 percent of\nits time to live or if the token is older than 24 hours.Defaults to 1 hour\nand must be at least 10 minutes.\n+optional" + "title": "expirationSeconds is the requested duration of validity of the service\naccount token. As the token approaches expiration, the kubelet volume\nplugin will proactively rotate the service account token. The kubelet will\nstart trying to rotate the token if the token is older than 80 percent of\nits time to live or if the token is older than 24 hours.Defaults to 1 hour\nand must be at least 10 minutes.\n+optional" }, "path": { "type": "string", - "description": "Path is the path relative to the mount point of the file to project the\ntoken into." + "description": "path is the path relative to the mount point of the file to project the\ntoken into." } }, "description": "ServiceAccountTokenProjection represents a projected service account token\nvolume. This projection can be used to insert a service account token into\nthe pods runtime filesystem for use against APIs (Kubernetes API Server or\notherwise)." @@ -3818,23 +3882,23 @@ "properties": { "volumeName": { "type": "string", - "description": "VolumeName is the human-readable name of the StorageOS volume. Volume\nnames are only unique within a namespace." + "description": "volumeName is the human-readable name of the StorageOS volume. Volume\nnames are only unique within a namespace." }, "volumeNamespace": { "type": "string", - "title": "VolumeNamespace specifies the scope of the volume within StorageOS. If no\nnamespace is specified then the Pod's namespace will be used. This allows the\nKubernetes name scoping to be mirrored within StorageOS for tighter integration.\nSet VolumeName to any name to override the default behaviour.\nSet to \"default\" if you are not using namespaces within StorageOS.\nNamespaces that do not pre-exist within StorageOS will be created.\n+optional" + "title": "volumeNamespace specifies the scope of the volume within StorageOS. If no\nnamespace is specified then the Pod's namespace will be used. This allows the\nKubernetes name scoping to be mirrored within StorageOS for tighter integration.\nSet VolumeName to any name to override the default behaviour.\nSet to \"default\" if you are not using namespaces within StorageOS.\nNamespaces that do not pre-exist within StorageOS will be created.\n+optional" }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" + "title": "fsType is the filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" }, "readOnly": { "type": "boolean", - "title": "Defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" + "title": "readOnly defaults to false (read/write). ReadOnly here will force\nthe ReadOnly setting in VolumeMounts.\n+optional" }, "secretRef": { "$ref": "#/definitions/k8s.io.api.core.v1.LocalObjectReference", - "title": "SecretRef specifies the secret to use for obtaining the StorageOS API\ncredentials. If not specified, default values will be attempted.\n+optional" + "title": "secretRef specifies the secret to use for obtaining the StorageOS API\ncredentials. If not specified, default values will be attempted.\n+optional" } }, "description": "Represents a StorageOS persistent volume resource." @@ -3900,11 +3964,11 @@ "maxSkew": { "type": "integer", "format": "int32", - "description": "MaxSkew describes the degree to which pods may be unevenly distributed.\nWhen `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference\nbetween the number of matching pods in the target topology and the global minimum.\nFor example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same\nlabelSelector spread as 1/1/0:\n+-------+-------+-------+\n| zone1 | zone2 | zone3 |\n+-------+-------+-------+\n| P | P | |\n+-------+-------+-------+\n- if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1;\nscheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2)\nviolate MaxSkew(1).\n- if MaxSkew is 2, incoming pod can be scheduled onto any zone.\nWhen `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence\nto topologies that satisfy it.\nIt's a required field. Default value is 1 and 0 is not allowed." + "description": "MaxSkew describes the degree to which pods may be unevenly distributed.\nWhen `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference\nbetween the number of matching pods in the target topology and the global minimum.\nThe global minimum is the minimum number of matching pods in an eligible domain\nor zero if the number of eligible domains is less than MinDomains.\nFor example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same\nlabelSelector spread as 2/2/1:\nIn this case, the global minimum is 1.\n+-------+-------+-------+\n| zone1 | zone2 | zone3 |\n+-------+-------+-------+\n| P P | P P | P |\n+-------+-------+-------+\n- if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2;\nscheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2)\nviolate MaxSkew(1).\n- if MaxSkew is 2, incoming pod can be scheduled onto any zone.\nWhen `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence\nto topologies that satisfy it.\nIt's a required field. Default value is 1 and 0 is not allowed." }, "topologyKey": { "type": "string", - "description": "TopologyKey is the key of node labels. Nodes that have a label with this key\nand identical values are considered to be in the same topology.\nWe consider each \u003ckey, value\u003e as a \"bucket\", and try to put balanced number\nof pods into each bucket.\nIt's a required field." + "description": "TopologyKey is the key of node labels. Nodes that have a label with this key\nand identical values are considered to be in the same topology.\nWe consider each \u003ckey, value\u003e as a \"bucket\", and try to put balanced number\nof pods into each bucket.\nWe define a domain as a particular instance of a topology.\nAlso, we define an eligible domain as a domain whose nodes match the node selector.\ne.g. If TopologyKey is \"kubernetes.io/hostname\", each Node is a domain of that topology.\nAnd, if TopologyKey is \"topology.kubernetes.io/zone\", each zone is a domain of that topology.\nIt's a required field." }, "whenUnsatisfiable": { "type": "string", @@ -3913,6 +3977,11 @@ "labelSelector": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector", "title": "LabelSelector is used to find matching pods.\nPods that match this label selector are counted to determine the number of pods\nin their corresponding topology domain.\n+optional" + }, + "minDomains": { + "type": "integer", + "format": "int32", + "description": "MinDomains indicates a minimum number of eligible domains.\nWhen the number of eligible domains with matching topology keys is less than minDomains,\nPod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed.\nAnd when the number of eligible domains with matching topology keys equals or greater than minDomains,\nthis value has no effect on scheduling.\nAs a result, when the number of eligible domains is less than minDomains,\nscheduler won't schedule more than maxSkew Pods to those domains.\nIf value is nil, the constraint behaves as if MinDomains is equal to 1.\nValid values are integers greater than 0.\nWhen value is not nil, WhenUnsatisfiable must be DoNotSchedule.\n\nFor example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same\nlabelSelector spread as 2/2/2:\n+-------+-------+-------+\n| zone1 | zone2 | zone3 |\n+-------+-------+-------+\n| P P | P P | P P |\n+-------+-------+-------+\nThe number of domains is less than 5(MinDomains), so \"global minimum\" is treated as 0.\nIn this situation, new pod with the same labelSelector cannot be scheduled,\nbecause computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones,\nit will violate MaxSkew.\n\nThis is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate.\n+optional" } }, "description": "TopologySpreadConstraint specifies how to spread matching pods among the given topology." @@ -3940,11 +4009,11 @@ "properties": { "name": { "type": "string", - "title": "Volume's name.\nMust be a DNS_LABEL and unique within the pod.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" + "title": "name of the volume.\nMust be a DNS_LABEL and unique within the pod.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names" }, "volumeSource": { "$ref": "#/definitions/k8s.io.api.core.v1.VolumeSource", - "description": "VolumeSource represents the location and type of the mounted volume.\nIf not specified, the Volume is implied to be an EmptyDir.\nThis implied behavior is deprecated and will be removed in a future version." + "description": "volumeSource represents the location and type of the mounted volume.\nIf not specified, the Volume is implied to be an EmptyDir.\nThis implied behavior is deprecated and will be removed in a future version." } }, "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod." @@ -3998,19 +4067,19 @@ "properties": { "secret": { "$ref": "#/definitions/k8s.io.api.core.v1.SecretProjection", - "title": "information about the secret data to project\n+optional" + "title": "secret information about the secret data to project\n+optional" }, "downwardAPI": { "$ref": "#/definitions/k8s.io.api.core.v1.DownwardAPIProjection", - "title": "information about the downwardAPI data to project\n+optional" + "title": "downwardAPI information about the downwardAPI data to project\n+optional" }, "configMap": { "$ref": "#/definitions/k8s.io.api.core.v1.ConfigMapProjection", - "title": "information about the configMap data to project\n+optional" + "title": "configMap information about the configMap data to project\n+optional" }, "serviceAccountToken": { "$ref": "#/definitions/k8s.io.api.core.v1.ServiceAccountTokenProjection", - "title": "information about the serviceAccountToken data to project\n+optional" + "title": "serviceAccountToken is information about the serviceAccountToken data to project\n+optional" } }, "title": "Projection that may be projected along with other supported volume types" @@ -4020,119 +4089,119 @@ "properties": { "hostPath": { "$ref": "#/definitions/k8s.io.api.core.v1.HostPathVolumeSource", - "title": "HostPath represents a pre-existing file or directory on the host\nmachine that is directly exposed to the container. This is generally\nused for system agents or other privileged things that are allowed\nto see the host machine. Most containers will NOT need this.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n---\nTODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not\nmount host directories as read/write.\n+optional" + "title": "hostPath represents a pre-existing file or directory on the host\nmachine that is directly exposed to the container. This is generally\nused for system agents or other privileged things that are allowed\nto see the host machine. Most containers will NOT need this.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n---\nTODO(jonesdl) We need to restrict who can use host directory mounts and who can/can not\nmount host directories as read/write.\n+optional" }, "emptyDir": { "$ref": "#/definitions/k8s.io.api.core.v1.EmptyDirVolumeSource", - "title": "EmptyDir represents a temporary directory that shares a pod's lifetime.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir\n+optional" + "title": "emptyDir represents a temporary directory that shares a pod's lifetime.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir\n+optional" }, "gcePersistentDisk": { "$ref": "#/definitions/k8s.io.api.core.v1.GCEPersistentDiskVolumeSource", - "title": "GCEPersistentDisk represents a GCE Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" + "title": "gcePersistentDisk represents a GCE Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk\n+optional" }, "awsElasticBlockStore": { "$ref": "#/definitions/k8s.io.api.core.v1.AWSElasticBlockStoreVolumeSource", - "title": "AWSElasticBlockStore represents an AWS Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n+optional" + "title": "awsElasticBlockStore represents an AWS Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore\n+optional" }, "gitRepo": { "$ref": "#/definitions/k8s.io.api.core.v1.GitRepoVolumeSource", - "title": "GitRepo represents a git repository at a particular revision.\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an\nEmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir\ninto the Pod's container.\n+optional" + "title": "gitRepo represents a git repository at a particular revision.\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an\nEmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir\ninto the Pod's container.\n+optional" }, "secret": { "$ref": "#/definitions/k8s.io.api.core.v1.SecretVolumeSource", - "title": "Secret represents a secret that should populate this volume.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#secret\n+optional" + "title": "secret represents a secret that should populate this volume.\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#secret\n+optional" }, "nfs": { "$ref": "#/definitions/k8s.io.api.core.v1.NFSVolumeSource", - "title": "NFS represents an NFS mount on the host that shares a pod's lifetime\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs\n+optional" + "title": "nfs represents an NFS mount on the host that shares a pod's lifetime\nMore info: https://kubernetes.io/docs/concepts/storage/volumes#nfs\n+optional" }, "iscsi": { "$ref": "#/definitions/k8s.io.api.core.v1.ISCSIVolumeSource", - "title": "ISCSI represents an ISCSI Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://examples.k8s.io/volumes/iscsi/README.md\n+optional" + "title": "iscsi represents an ISCSI Disk resource that is attached to a\nkubelet's host machine and then exposed to the pod.\nMore info: https://examples.k8s.io/volumes/iscsi/README.md\n+optional" }, "glusterfs": { "$ref": "#/definitions/k8s.io.api.core.v1.GlusterfsVolumeSource", - "title": "Glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md\n+optional" + "title": "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime.\nMore info: https://examples.k8s.io/volumes/glusterfs/README.md\n+optional" }, "persistentVolumeClaim": { "$ref": "#/definitions/k8s.io.api.core.v1.PersistentVolumeClaimVolumeSource", - "title": "PersistentVolumeClaimVolumeSource represents a reference to a\nPersistentVolumeClaim in the same namespace.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims\n+optional" + "title": "persistentVolumeClaimVolumeSource represents a reference to a\nPersistentVolumeClaim in the same namespace.\nMore info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims\n+optional" }, "rbd": { "$ref": "#/definitions/k8s.io.api.core.v1.RBDVolumeSource", - "title": "RBD represents a Rados Block Device mount on the host that shares a pod's lifetime.\nMore info: https://examples.k8s.io/volumes/rbd/README.md\n+optional" + "title": "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime.\nMore info: https://examples.k8s.io/volumes/rbd/README.md\n+optional" }, "flexVolume": { "$ref": "#/definitions/k8s.io.api.core.v1.FlexVolumeSource", - "title": "FlexVolume represents a generic volume resource that is\nprovisioned/attached using an exec based plugin.\n+optional" + "title": "flexVolume represents a generic volume resource that is\nprovisioned/attached using an exec based plugin.\n+optional" }, "cinder": { "$ref": "#/definitions/k8s.io.api.core.v1.CinderVolumeSource", - "title": "Cinder represents a cinder volume attached and mounted on kubelets host machine.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" + "title": "cinder represents a cinder volume attached and mounted on kubelets host machine.\nMore info: https://examples.k8s.io/mysql-cinder-pd/README.md\n+optional" }, "cephfs": { "$ref": "#/definitions/k8s.io.api.core.v1.CephFSVolumeSource", - "title": "CephFS represents a Ceph FS mount on the host that shares a pod's lifetime\n+optional" + "title": "cephFS represents a Ceph FS mount on the host that shares a pod's lifetime\n+optional" }, "flocker": { "$ref": "#/definitions/k8s.io.api.core.v1.FlockerVolumeSource", - "title": "Flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running\n+optional" + "title": "flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running\n+optional" }, "downwardAPI": { "$ref": "#/definitions/k8s.io.api.core.v1.DownwardAPIVolumeSource", - "title": "DownwardAPI represents downward API about the pod that should populate this volume\n+optional" + "title": "downwardAPI represents downward API about the pod that should populate this volume\n+optional" }, "fc": { "$ref": "#/definitions/k8s.io.api.core.v1.FCVolumeSource", - "title": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.\n+optional" + "title": "fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.\n+optional" }, "azureFile": { "$ref": "#/definitions/k8s.io.api.core.v1.AzureFileVolumeSource", - "title": "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.\n+optional" + "title": "azureFile represents an Azure File Service mount on the host and bind mount to the pod.\n+optional" }, "configMap": { "$ref": "#/definitions/k8s.io.api.core.v1.ConfigMapVolumeSource", - "title": "ConfigMap represents a configMap that should populate this volume\n+optional" + "title": "configMap represents a configMap that should populate this volume\n+optional" }, "vsphereVolume": { "$ref": "#/definitions/k8s.io.api.core.v1.VsphereVirtualDiskVolumeSource", - "title": "VsphereVolume represents a vSphere volume attached and mounted on kubelets host machine\n+optional" + "title": "vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine\n+optional" }, "quobyte": { "$ref": "#/definitions/k8s.io.api.core.v1.QuobyteVolumeSource", - "title": "Quobyte represents a Quobyte mount on the host that shares a pod's lifetime\n+optional" + "title": "quobyte represents a Quobyte mount on the host that shares a pod's lifetime\n+optional" }, "azureDisk": { "$ref": "#/definitions/k8s.io.api.core.v1.AzureDiskVolumeSource", - "title": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.\n+optional" + "title": "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.\n+optional" }, "photonPersistentDisk": { "$ref": "#/definitions/k8s.io.api.core.v1.PhotonPersistentDiskVolumeSource", - "title": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" + "title": "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine" }, "projected": { "$ref": "#/definitions/k8s.io.api.core.v1.ProjectedVolumeSource", - "title": "Items for all in one resources secrets, configmaps, and downward API" + "title": "projected items for all in one resources secrets, configmaps, and downward API" }, "portworxVolume": { "$ref": "#/definitions/k8s.io.api.core.v1.PortworxVolumeSource", - "title": "PortworxVolume represents a portworx volume attached and mounted on kubelets host machine\n+optional" + "title": "portworxVolume represents a portworx volume attached and mounted on kubelets host machine\n+optional" }, "scaleIO": { "$ref": "#/definitions/k8s.io.api.core.v1.ScaleIOVolumeSource", - "title": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.\n+optional" + "title": "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.\n+optional" }, "storageos": { "$ref": "#/definitions/k8s.io.api.core.v1.StorageOSVolumeSource", - "title": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.\n+optional" + "title": "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.\n+optional" }, "csi": { "$ref": "#/definitions/k8s.io.api.core.v1.CSIVolumeSource", - "title": "CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).\n+optional" + "title": "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).\n+optional" }, "ephemeral": { "$ref": "#/definitions/k8s.io.api.core.v1.EphemeralVolumeSource", - "description": "Ephemeral represents a volume that is handled by a cluster storage driver.\nThe volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,\nand deleted when the pod is removed.\n\nUse this if:\na) the volume is only needed while the pod runs,\nb) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and\nd) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific\nAPIs for volumes that persist for longer than the lifecycle\nof an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to\nbe used that way - see the documentation of the driver for\nmore information.\n\nA pod can use both types of ephemeral volumes and\npersistent volumes at the same time.\n\n+optional" + "description": "ephemeral represents a volume that is handled by a cluster storage driver.\nThe volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,\nand deleted when the pod is removed.\n\nUse this if:\na) the volume is only needed while the pod runs,\nb) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and\nd) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific\nAPIs for volumes that persist for longer than the lifecycle\nof an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to\nbe used that way - see the documentation of the driver for\nmore information.\n\nA pod can use both types of ephemeral volumes and\npersistent volumes at the same time.\n\n+optional" } }, "description": "Represents the source of a volume to mount.\nOnly one of its members may be specified." @@ -4142,19 +4211,19 @@ "properties": { "volumePath": { "type": "string", - "title": "Path that identifies vSphere volume vmdk" + "title": "volumePath is the path that identifies vSphere volume vmdk" }, "fsType": { "type": "string", - "title": "Filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" + "title": "fsType is filesystem type to mount.\nMust be a filesystem type supported by the host operating system.\nEx. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.\n+optional" }, "storagePolicyName": { "type": "string", - "title": "Storage Policy Based Management (SPBM) profile name.\n+optional" + "title": "storagePolicyName is the storage Policy Based Management (SPBM) profile name.\n+optional" }, "storagePolicyID": { "type": "string", - "title": "Storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.\n+optional" + "title": "storagePolicyID is the storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.\n+optional" } }, "description": "Represents a vSphere volume resource." @@ -4274,7 +4343,7 @@ }, "time": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", - "title": "Time is timestamp of when these fields were set. It should always be empty if Operation is 'Apply'\n+optional" + "title": "Time is the timestamp of when the ManagedFields entry was added. The\ntimestamp will also be updated if a field is added, the manager\nchanges any of the owned fields value or removes a field. The\ntimestamp does not update when a field is removed from the entry\nbecause another manager took it over.\n+optional" }, "fieldsType": { "type": "string", @@ -4300,7 +4369,7 @@ }, "generateName": { "type": "string", - "description": "GenerateName is an optional prefix, used by the server, to generate a unique\nname ONLY IF the Name field has not been provided.\nIf this field is used, the name returned to the client will be different\nthan the name passed. This value will also be combined with a unique suffix.\nThe provided value has the same validation rules as the Name field,\nand may be truncated by the length of the suffix required to make the value\nunique on the server.\n\nIf this field is specified and the generated name exists, the server will\nNOT return a 409 - instead, it will either return 201 Created or 500 with Reason\nServerTimeout indicating a unique name could not be found in the time allotted, and the client\nshould retry (optionally after the time indicated in the Retry-After header).\n\nApplied only if Name is not specified.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency\n+optional" + "description": "GenerateName is an optional prefix, used by the server, to generate a unique\nname ONLY IF the Name field has not been provided.\nIf this field is used, the name returned to the client will be different\nthan the name passed. This value will also be combined with a unique suffix.\nThe provided value has the same validation rules as the Name field,\nand may be truncated by the length of the suffix required to make the value\nunique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency\n+optional" }, "namespace": { "type": "string", @@ -4308,7 +4377,7 @@ }, "selfLink": { "type": "string", - "description": "SelfLink is a URL representing this object.\nPopulated by the system.\nRead-only.\n\nDEPRECATED\nKubernetes will stop propagating this field in 1.20 release and the field is planned\nto be removed in 1.21 release.\n+optional" + "title": "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.\n+optional" }, "uid": { "type": "string", @@ -4366,7 +4435,7 @@ }, "clusterName": { "type": "string", - "title": "The name of the cluster which the object belongs to.\nThis is used to distinguish resources with same name and namespace in different clusters.\nThis field is not set anywhere right now and apiserver is going to ignore it if set in create or update request.\n+optional" + "description": "Deprecated: ClusterName is a legacy field that was always cleared by\nthe system and never used; it will be removed completely in 1.25.\n\nThe name in the go struct is changed to help clients detect\naccidental use.\n\n+optional" }, "managedFields": { "type": "array", @@ -4403,7 +4472,7 @@ }, "blockOwnerDeletion": { "type": "boolean", - "title": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then\nthe owner cannot be deleted from the key-value store until this\nreference is removed.\nDefaults to false.\nTo set this field, a user needs \"delete\" permission of the owner,\notherwise 422 (Unprocessable Entity) will be returned.\n+optional" + "title": "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then\nthe owner cannot be deleted from the key-value store until this\nreference is removed.\nSee https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion\nfor how the garbage collector interacts with this field and enforces the foreground deletion.\nDefaults to false.\nTo set this field, a user needs \"delete\" permission of the owner,\notherwise 422 (Unprocessable Entity) will be returned.\n+optional" } }, "title": "OwnerReference contains enough information to let you identify an owning\nobject. An owning object must be in the same namespace as the dependent, or\nbe cluster-scoped, so there is no namespace field.\n+structType=atomic" diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index 96ebd0bc27..4393612dcd 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -35,7 +35,9 @@ API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutExperimentStepAnalysisTemplateRef,Args API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,Conditions API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,PauseConditions -API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,SetHeaderRouting,Match +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutTrafficRouting,ManagedRoutes +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,SetHeaderRoute,Match +API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,SetMirrorRoute,Match API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,TLSRoute,SNIHosts API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,TrafficWeights,Additional API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,WebMetric,Headers diff --git a/pkg/apis/rollouts/v1alpha1/analysis_types.go b/pkg/apis/rollouts/v1alpha1/analysis_types.go index 7b6b8e87c4..7d5931e458 100644 --- a/pkg/apis/rollouts/v1alpha1/analysis_types.go +++ b/pkg/apis/rollouts/v1alpha1/analysis_types.go @@ -13,7 +13,7 @@ import ( // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:resource:path=clusteranalysistemplates,shortName=cat +// +kubebuilder:resource:path=clusteranalysistemplates,shortName=cat,scope=Cluster // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time since resource was created" type ClusterAnalysisTemplate struct { metav1.TypeMeta `json:",inline"` @@ -170,6 +170,8 @@ type MetricProvider struct { CloudWatch *CloudWatchMetric `json:"cloudWatch,omitempty" protobuf:"bytes,8,opt,name=cloudWatch"` // Graphite specifies the Graphite metric to query Graphite *GraphiteMetric `json:"graphite,omitempty" protobuf:"bytes,9,opt,name=graphite"` + // Influxdb specifies the influxdb metric to query + Influxdb *InfluxdbMetric `json:"influxdb,omitempty" protobuf:"bytes,10,opt,name=influxdb"` } // AnalysisPhase is the overall phase of an AnalysisRun, MetricResult, or Measurement @@ -232,6 +234,14 @@ type GraphiteMetric struct { Query string `json:"query,omitempty" protobuf:"bytes,2,opt,name=query"` } +// InfluxdbMetric defines the InfluxDB Flux query to perform canary analysis +type InfluxdbMetric struct { + // Profile is the name of the secret holding InfluxDB account configuration + Profile string `json:"profile,omitempty" protobuf:"bytes,1,opt,name=profile"` + // Query is a raw InfluxDB flux query to perform + Query string `json:"query,omitempty" protobuf:"bytes,2,opt,name=query"` +} + // CloudWatchMetric defines the cloudwatch query to perform canary analysis type CloudWatchMetric struct { Interval DurationString `json:"interval,omitempty" protobuf:"bytes,1,opt,name=interval,casttype=DurationString"` diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index ae221882a7..fc7ecb4fbb 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -1280,10 +1280,38 @@ func (m *HeaderRoutingMatch) XXX_DiscardUnknown() { var xxx_messageInfo_HeaderRoutingMatch proto.InternalMessageInfo +func (m *InfluxdbMetric) Reset() { *m = InfluxdbMetric{} } +func (*InfluxdbMetric) ProtoMessage() {} +func (*InfluxdbMetric) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{44} +} +func (m *InfluxdbMetric) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *InfluxdbMetric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *InfluxdbMetric) XXX_Merge(src proto.Message) { + xxx_messageInfo_InfluxdbMetric.Merge(m, src) +} +func (m *InfluxdbMetric) XXX_Size() int { + return m.Size() +} +func (m *InfluxdbMetric) XXX_DiscardUnknown() { + xxx_messageInfo_InfluxdbMetric.DiscardUnknown(m) +} + +var xxx_messageInfo_InfluxdbMetric proto.InternalMessageInfo + func (m *IstioDestinationRule) Reset() { *m = IstioDestinationRule{} } func (*IstioDestinationRule) ProtoMessage() {} func (*IstioDestinationRule) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{44} + return fileDescriptor_e0e705f843545fab, []int{45} } func (m *IstioDestinationRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1311,7 +1339,7 @@ var xxx_messageInfo_IstioDestinationRule proto.InternalMessageInfo func (m *IstioTrafficRouting) Reset() { *m = IstioTrafficRouting{} } func (*IstioTrafficRouting) ProtoMessage() {} func (*IstioTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{45} + return fileDescriptor_e0e705f843545fab, []int{46} } func (m *IstioTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1339,7 +1367,7 @@ var xxx_messageInfo_IstioTrafficRouting proto.InternalMessageInfo func (m *IstioVirtualService) Reset() { *m = IstioVirtualService{} } func (*IstioVirtualService) ProtoMessage() {} func (*IstioVirtualService) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{46} + return fileDescriptor_e0e705f843545fab, []int{47} } func (m *IstioVirtualService) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1367,7 +1395,7 @@ var xxx_messageInfo_IstioVirtualService proto.InternalMessageInfo func (m *JobMetric) Reset() { *m = JobMetric{} } func (*JobMetric) ProtoMessage() {} func (*JobMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{47} + return fileDescriptor_e0e705f843545fab, []int{48} } func (m *JobMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1395,7 +1423,7 @@ var xxx_messageInfo_JobMetric proto.InternalMessageInfo func (m *KayentaMetric) Reset() { *m = KayentaMetric{} } func (*KayentaMetric) ProtoMessage() {} func (*KayentaMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{48} + return fileDescriptor_e0e705f843545fab, []int{49} } func (m *KayentaMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1423,7 +1451,7 @@ var xxx_messageInfo_KayentaMetric proto.InternalMessageInfo func (m *KayentaScope) Reset() { *m = KayentaScope{} } func (*KayentaScope) ProtoMessage() {} func (*KayentaScope) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{49} + return fileDescriptor_e0e705f843545fab, []int{50} } func (m *KayentaScope) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1451,7 +1479,7 @@ var xxx_messageInfo_KayentaScope proto.InternalMessageInfo func (m *KayentaThreshold) Reset() { *m = KayentaThreshold{} } func (*KayentaThreshold) ProtoMessage() {} func (*KayentaThreshold) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{50} + return fileDescriptor_e0e705f843545fab, []int{51} } func (m *KayentaThreshold) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1476,10 +1504,38 @@ func (m *KayentaThreshold) XXX_DiscardUnknown() { var xxx_messageInfo_KayentaThreshold proto.InternalMessageInfo +func (m *MangedRoutes) Reset() { *m = MangedRoutes{} } +func (*MangedRoutes) ProtoMessage() {} +func (*MangedRoutes) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{52} +} +func (m *MangedRoutes) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MangedRoutes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *MangedRoutes) XXX_Merge(src proto.Message) { + xxx_messageInfo_MangedRoutes.Merge(m, src) +} +func (m *MangedRoutes) XXX_Size() int { + return m.Size() +} +func (m *MangedRoutes) XXX_DiscardUnknown() { + xxx_messageInfo_MangedRoutes.DiscardUnknown(m) +} + +var xxx_messageInfo_MangedRoutes proto.InternalMessageInfo + func (m *Measurement) Reset() { *m = Measurement{} } func (*Measurement) ProtoMessage() {} func (*Measurement) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{51} + return fileDescriptor_e0e705f843545fab, []int{53} } func (m *Measurement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1507,7 +1563,7 @@ var xxx_messageInfo_Measurement proto.InternalMessageInfo func (m *MeasurementRetention) Reset() { *m = MeasurementRetention{} } func (*MeasurementRetention) ProtoMessage() {} func (*MeasurementRetention) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{52} + return fileDescriptor_e0e705f843545fab, []int{54} } func (m *MeasurementRetention) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1535,7 +1591,7 @@ var xxx_messageInfo_MeasurementRetention proto.InternalMessageInfo func (m *Metric) Reset() { *m = Metric{} } func (*Metric) ProtoMessage() {} func (*Metric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{53} + return fileDescriptor_e0e705f843545fab, []int{55} } func (m *Metric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1563,7 +1619,7 @@ var xxx_messageInfo_Metric proto.InternalMessageInfo func (m *MetricProvider) Reset() { *m = MetricProvider{} } func (*MetricProvider) ProtoMessage() {} func (*MetricProvider) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{54} + return fileDescriptor_e0e705f843545fab, []int{56} } func (m *MetricProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1591,7 +1647,7 @@ var xxx_messageInfo_MetricProvider proto.InternalMessageInfo func (m *MetricResult) Reset() { *m = MetricResult{} } func (*MetricResult) ProtoMessage() {} func (*MetricResult) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{55} + return fileDescriptor_e0e705f843545fab, []int{57} } func (m *MetricResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1619,7 +1675,7 @@ var xxx_messageInfo_MetricResult proto.InternalMessageInfo func (m *NewRelicMetric) Reset() { *m = NewRelicMetric{} } func (*NewRelicMetric) ProtoMessage() {} func (*NewRelicMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{56} + return fileDescriptor_e0e705f843545fab, []int{58} } func (m *NewRelicMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1647,7 +1703,7 @@ var xxx_messageInfo_NewRelicMetric proto.InternalMessageInfo func (m *NginxTrafficRouting) Reset() { *m = NginxTrafficRouting{} } func (*NginxTrafficRouting) ProtoMessage() {} func (*NginxTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{57} + return fileDescriptor_e0e705f843545fab, []int{59} } func (m *NginxTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1675,7 +1731,7 @@ var xxx_messageInfo_NginxTrafficRouting proto.InternalMessageInfo func (m *ObjectRef) Reset() { *m = ObjectRef{} } func (*ObjectRef) ProtoMessage() {} func (*ObjectRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{58} + return fileDescriptor_e0e705f843545fab, []int{60} } func (m *ObjectRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1703,7 +1759,7 @@ var xxx_messageInfo_ObjectRef proto.InternalMessageInfo func (m *PauseCondition) Reset() { *m = PauseCondition{} } func (*PauseCondition) ProtoMessage() {} func (*PauseCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{59} + return fileDescriptor_e0e705f843545fab, []int{61} } func (m *PauseCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1731,7 +1787,7 @@ var xxx_messageInfo_PauseCondition proto.InternalMessageInfo func (m *PingPongSpec) Reset() { *m = PingPongSpec{} } func (*PingPongSpec) ProtoMessage() {} func (*PingPongSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{60} + return fileDescriptor_e0e705f843545fab, []int{62} } func (m *PingPongSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1759,7 +1815,7 @@ var xxx_messageInfo_PingPongSpec proto.InternalMessageInfo func (m *PodTemplateMetadata) Reset() { *m = PodTemplateMetadata{} } func (*PodTemplateMetadata) ProtoMessage() {} func (*PodTemplateMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{61} + return fileDescriptor_e0e705f843545fab, []int{63} } func (m *PodTemplateMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1789,7 +1845,7 @@ func (m *PreferredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*PreferredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*PreferredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{62} + return fileDescriptor_e0e705f843545fab, []int{64} } func (m *PreferredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1817,7 +1873,7 @@ var xxx_messageInfo_PreferredDuringSchedulingIgnoredDuringExecution proto.Intern func (m *PrometheusMetric) Reset() { *m = PrometheusMetric{} } func (*PrometheusMetric) ProtoMessage() {} func (*PrometheusMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{63} + return fileDescriptor_e0e705f843545fab, []int{65} } func (m *PrometheusMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1847,7 +1903,7 @@ func (m *RequiredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*RequiredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*RequiredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{64} + return fileDescriptor_e0e705f843545fab, []int{66} } func (m *RequiredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1875,7 +1931,7 @@ var xxx_messageInfo_RequiredDuringSchedulingIgnoredDuringExecution proto.Interna func (m *Rollout) Reset() { *m = Rollout{} } func (*Rollout) ProtoMessage() {} func (*Rollout) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{65} + return fileDescriptor_e0e705f843545fab, []int{67} } func (m *Rollout) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1903,7 +1959,7 @@ var xxx_messageInfo_Rollout proto.InternalMessageInfo func (m *RolloutAnalysis) Reset() { *m = RolloutAnalysis{} } func (*RolloutAnalysis) ProtoMessage() {} func (*RolloutAnalysis) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{66} + return fileDescriptor_e0e705f843545fab, []int{68} } func (m *RolloutAnalysis) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1931,7 +1987,7 @@ var xxx_messageInfo_RolloutAnalysis proto.InternalMessageInfo func (m *RolloutAnalysisBackground) Reset() { *m = RolloutAnalysisBackground{} } func (*RolloutAnalysisBackground) ProtoMessage() {} func (*RolloutAnalysisBackground) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{67} + return fileDescriptor_e0e705f843545fab, []int{69} } func (m *RolloutAnalysisBackground) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1959,7 +2015,7 @@ var xxx_messageInfo_RolloutAnalysisBackground proto.InternalMessageInfo func (m *RolloutAnalysisRunStatus) Reset() { *m = RolloutAnalysisRunStatus{} } func (*RolloutAnalysisRunStatus) ProtoMessage() {} func (*RolloutAnalysisRunStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{68} + return fileDescriptor_e0e705f843545fab, []int{70} } func (m *RolloutAnalysisRunStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1987,7 +2043,7 @@ var xxx_messageInfo_RolloutAnalysisRunStatus proto.InternalMessageInfo func (m *RolloutAnalysisTemplate) Reset() { *m = RolloutAnalysisTemplate{} } func (*RolloutAnalysisTemplate) ProtoMessage() {} func (*RolloutAnalysisTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{69} + return fileDescriptor_e0e705f843545fab, []int{71} } func (m *RolloutAnalysisTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2015,7 +2071,7 @@ var xxx_messageInfo_RolloutAnalysisTemplate proto.InternalMessageInfo func (m *RolloutCondition) Reset() { *m = RolloutCondition{} } func (*RolloutCondition) ProtoMessage() {} func (*RolloutCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{70} + return fileDescriptor_e0e705f843545fab, []int{72} } func (m *RolloutCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2043,7 +2099,7 @@ var xxx_messageInfo_RolloutCondition proto.InternalMessageInfo func (m *RolloutExperimentStep) Reset() { *m = RolloutExperimentStep{} } func (*RolloutExperimentStep) ProtoMessage() {} func (*RolloutExperimentStep) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{71} + return fileDescriptor_e0e705f843545fab, []int{73} } func (m *RolloutExperimentStep) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2073,7 +2129,7 @@ func (m *RolloutExperimentStepAnalysisTemplateRef) Reset() { } func (*RolloutExperimentStepAnalysisTemplateRef) ProtoMessage() {} func (*RolloutExperimentStepAnalysisTemplateRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{72} + return fileDescriptor_e0e705f843545fab, []int{74} } func (m *RolloutExperimentStepAnalysisTemplateRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2101,7 +2157,7 @@ var xxx_messageInfo_RolloutExperimentStepAnalysisTemplateRef proto.InternalMessa func (m *RolloutExperimentTemplate) Reset() { *m = RolloutExperimentTemplate{} } func (*RolloutExperimentTemplate) ProtoMessage() {} func (*RolloutExperimentTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{73} + return fileDescriptor_e0e705f843545fab, []int{75} } func (m *RolloutExperimentTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2129,7 +2185,7 @@ var xxx_messageInfo_RolloutExperimentTemplate proto.InternalMessageInfo func (m *RolloutList) Reset() { *m = RolloutList{} } func (*RolloutList) ProtoMessage() {} func (*RolloutList) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{74} + return fileDescriptor_e0e705f843545fab, []int{76} } func (m *RolloutList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2157,7 +2213,7 @@ var xxx_messageInfo_RolloutList proto.InternalMessageInfo func (m *RolloutPause) Reset() { *m = RolloutPause{} } func (*RolloutPause) ProtoMessage() {} func (*RolloutPause) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{75} + return fileDescriptor_e0e705f843545fab, []int{77} } func (m *RolloutPause) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2185,7 +2241,7 @@ var xxx_messageInfo_RolloutPause proto.InternalMessageInfo func (m *RolloutSpec) Reset() { *m = RolloutSpec{} } func (*RolloutSpec) ProtoMessage() {} func (*RolloutSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{76} + return fileDescriptor_e0e705f843545fab, []int{78} } func (m *RolloutSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2213,7 +2269,7 @@ var xxx_messageInfo_RolloutSpec proto.InternalMessageInfo func (m *RolloutStatus) Reset() { *m = RolloutStatus{} } func (*RolloutStatus) ProtoMessage() {} func (*RolloutStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{77} + return fileDescriptor_e0e705f843545fab, []int{79} } func (m *RolloutStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2241,7 +2297,7 @@ var xxx_messageInfo_RolloutStatus proto.InternalMessageInfo func (m *RolloutStrategy) Reset() { *m = RolloutStrategy{} } func (*RolloutStrategy) ProtoMessage() {} func (*RolloutStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{78} + return fileDescriptor_e0e705f843545fab, []int{80} } func (m *RolloutStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2269,7 +2325,7 @@ var xxx_messageInfo_RolloutStrategy proto.InternalMessageInfo func (m *RolloutTrafficRouting) Reset() { *m = RolloutTrafficRouting{} } func (*RolloutTrafficRouting) ProtoMessage() {} func (*RolloutTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{79} + return fileDescriptor_e0e705f843545fab, []int{81} } func (m *RolloutTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2294,10 +2350,38 @@ func (m *RolloutTrafficRouting) XXX_DiscardUnknown() { var xxx_messageInfo_RolloutTrafficRouting proto.InternalMessageInfo +func (m *RouteMatch) Reset() { *m = RouteMatch{} } +func (*RouteMatch) ProtoMessage() {} +func (*RouteMatch) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{82} +} +func (m *RouteMatch) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RouteMatch) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RouteMatch) XXX_Merge(src proto.Message) { + xxx_messageInfo_RouteMatch.Merge(m, src) +} +func (m *RouteMatch) XXX_Size() int { + return m.Size() +} +func (m *RouteMatch) XXX_DiscardUnknown() { + xxx_messageInfo_RouteMatch.DiscardUnknown(m) +} + +var xxx_messageInfo_RouteMatch proto.InternalMessageInfo + func (m *RunSummary) Reset() { *m = RunSummary{} } func (*RunSummary) ProtoMessage() {} func (*RunSummary) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{80} + return fileDescriptor_e0e705f843545fab, []int{83} } func (m *RunSummary) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2325,7 +2409,7 @@ var xxx_messageInfo_RunSummary proto.InternalMessageInfo func (m *SMITrafficRouting) Reset() { *m = SMITrafficRouting{} } func (*SMITrafficRouting) ProtoMessage() {} func (*SMITrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{81} + return fileDescriptor_e0e705f843545fab, []int{84} } func (m *SMITrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2353,7 +2437,7 @@ var xxx_messageInfo_SMITrafficRouting proto.InternalMessageInfo func (m *ScopeDetail) Reset() { *m = ScopeDetail{} } func (*ScopeDetail) ProtoMessage() {} func (*ScopeDetail) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{82} + return fileDescriptor_e0e705f843545fab, []int{85} } func (m *ScopeDetail) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2381,7 +2465,7 @@ var xxx_messageInfo_ScopeDetail proto.InternalMessageInfo func (m *SecretKeyRef) Reset() { *m = SecretKeyRef{} } func (*SecretKeyRef) ProtoMessage() {} func (*SecretKeyRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{83} + return fileDescriptor_e0e705f843545fab, []int{86} } func (m *SecretKeyRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2409,7 +2493,7 @@ var xxx_messageInfo_SecretKeyRef proto.InternalMessageInfo func (m *SetCanaryScale) Reset() { *m = SetCanaryScale{} } func (*SetCanaryScale) ProtoMessage() {} func (*SetCanaryScale) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{84} + return fileDescriptor_e0e705f843545fab, []int{87} } func (m *SetCanaryScale) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2434,15 +2518,43 @@ func (m *SetCanaryScale) XXX_DiscardUnknown() { var xxx_messageInfo_SetCanaryScale proto.InternalMessageInfo -func (m *SetHeaderRouting) Reset() { *m = SetHeaderRouting{} } -func (*SetHeaderRouting) ProtoMessage() {} -func (*SetHeaderRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{85} +func (m *SetHeaderRoute) Reset() { *m = SetHeaderRoute{} } +func (*SetHeaderRoute) ProtoMessage() {} +func (*SetHeaderRoute) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{88} +} +func (m *SetHeaderRoute) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SetHeaderRoute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil } -func (m *SetHeaderRouting) XXX_Unmarshal(b []byte) error { +func (m *SetHeaderRoute) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetHeaderRoute.Merge(m, src) +} +func (m *SetHeaderRoute) XXX_Size() int { + return m.Size() +} +func (m *SetHeaderRoute) XXX_DiscardUnknown() { + xxx_messageInfo_SetHeaderRoute.DiscardUnknown(m) +} + +var xxx_messageInfo_SetHeaderRoute proto.InternalMessageInfo + +func (m *SetMirrorRoute) Reset() { *m = SetMirrorRoute{} } +func (*SetMirrorRoute) ProtoMessage() {} +func (*SetMirrorRoute) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{89} +} +func (m *SetMirrorRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *SetHeaderRouting) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *SetMirrorRoute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) if err != nil { @@ -2450,22 +2562,22 @@ func (m *SetHeaderRouting) XXX_Marshal(b []byte, deterministic bool) ([]byte, er } return b[:n], nil } -func (m *SetHeaderRouting) XXX_Merge(src proto.Message) { - xxx_messageInfo_SetHeaderRouting.Merge(m, src) +func (m *SetMirrorRoute) XXX_Merge(src proto.Message) { + xxx_messageInfo_SetMirrorRoute.Merge(m, src) } -func (m *SetHeaderRouting) XXX_Size() int { +func (m *SetMirrorRoute) XXX_Size() int { return m.Size() } -func (m *SetHeaderRouting) XXX_DiscardUnknown() { - xxx_messageInfo_SetHeaderRouting.DiscardUnknown(m) +func (m *SetMirrorRoute) XXX_DiscardUnknown() { + xxx_messageInfo_SetMirrorRoute.DiscardUnknown(m) } -var xxx_messageInfo_SetHeaderRouting proto.InternalMessageInfo +var xxx_messageInfo_SetMirrorRoute proto.InternalMessageInfo func (m *StickinessConfig) Reset() { *m = StickinessConfig{} } func (*StickinessConfig) ProtoMessage() {} func (*StickinessConfig) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{86} + return fileDescriptor_e0e705f843545fab, []int{90} } func (m *StickinessConfig) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2493,7 +2605,7 @@ var xxx_messageInfo_StickinessConfig proto.InternalMessageInfo func (m *StringMatch) Reset() { *m = StringMatch{} } func (*StringMatch) ProtoMessage() {} func (*StringMatch) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{87} + return fileDescriptor_e0e705f843545fab, []int{91} } func (m *StringMatch) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2521,7 +2633,7 @@ var xxx_messageInfo_StringMatch proto.InternalMessageInfo func (m *TLSRoute) Reset() { *m = TLSRoute{} } func (*TLSRoute) ProtoMessage() {} func (*TLSRoute) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{88} + return fileDescriptor_e0e705f843545fab, []int{92} } func (m *TLSRoute) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2549,7 +2661,7 @@ var xxx_messageInfo_TLSRoute proto.InternalMessageInfo func (m *TemplateService) Reset() { *m = TemplateService{} } func (*TemplateService) ProtoMessage() {} func (*TemplateService) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{89} + return fileDescriptor_e0e705f843545fab, []int{93} } func (m *TemplateService) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2577,7 +2689,7 @@ var xxx_messageInfo_TemplateService proto.InternalMessageInfo func (m *TemplateSpec) Reset() { *m = TemplateSpec{} } func (*TemplateSpec) ProtoMessage() {} func (*TemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{90} + return fileDescriptor_e0e705f843545fab, []int{94} } func (m *TemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2605,7 +2717,7 @@ var xxx_messageInfo_TemplateSpec proto.InternalMessageInfo func (m *TemplateStatus) Reset() { *m = TemplateStatus{} } func (*TemplateStatus) ProtoMessage() {} func (*TemplateStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{91} + return fileDescriptor_e0e705f843545fab, []int{95} } func (m *TemplateStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2633,7 +2745,7 @@ var xxx_messageInfo_TemplateStatus proto.InternalMessageInfo func (m *TraefikTrafficRouting) Reset() { *m = TraefikTrafficRouting{} } func (*TraefikTrafficRouting) ProtoMessage() {} func (*TraefikTrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{92} + return fileDescriptor_e0e705f843545fab, []int{96} } func (m *TraefikTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2661,7 +2773,7 @@ var xxx_messageInfo_TraefikTrafficRouting proto.InternalMessageInfo func (m *TrafficWeights) Reset() { *m = TrafficWeights{} } func (*TrafficWeights) ProtoMessage() {} func (*TrafficWeights) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{93} + return fileDescriptor_e0e705f843545fab, []int{97} } func (m *TrafficWeights) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2689,7 +2801,7 @@ var xxx_messageInfo_TrafficWeights proto.InternalMessageInfo func (m *ValueFrom) Reset() { *m = ValueFrom{} } func (*ValueFrom) ProtoMessage() {} func (*ValueFrom) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{94} + return fileDescriptor_e0e705f843545fab, []int{98} } func (m *ValueFrom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2717,7 +2829,7 @@ var xxx_messageInfo_ValueFrom proto.InternalMessageInfo func (m *WavefrontMetric) Reset() { *m = WavefrontMetric{} } func (*WavefrontMetric) ProtoMessage() {} func (*WavefrontMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{95} + return fileDescriptor_e0e705f843545fab, []int{99} } func (m *WavefrontMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2745,7 +2857,7 @@ var xxx_messageInfo_WavefrontMetric proto.InternalMessageInfo func (m *WebMetric) Reset() { *m = WebMetric{} } func (*WebMetric) ProtoMessage() {} func (*WebMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{96} + return fileDescriptor_e0e705f843545fab, []int{100} } func (m *WebMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2773,7 +2885,7 @@ var xxx_messageInfo_WebMetric proto.InternalMessageInfo func (m *WebMetricHeader) Reset() { *m = WebMetricHeader{} } func (*WebMetricHeader) ProtoMessage() {} func (*WebMetricHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{97} + return fileDescriptor_e0e705f843545fab, []int{101} } func (m *WebMetricHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2801,7 +2913,7 @@ var xxx_messageInfo_WebMetricHeader proto.InternalMessageInfo func (m *WeightDestination) Reset() { *m = WeightDestination{} } func (*WeightDestination) ProtoMessage() {} func (*WeightDestination) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{98} + return fileDescriptor_e0e705f843545fab, []int{102} } func (m *WeightDestination) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2871,6 +2983,7 @@ func init() { proto.RegisterType((*FieldRef)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.FieldRef") proto.RegisterType((*GraphiteMetric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.GraphiteMetric") proto.RegisterType((*HeaderRoutingMatch)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.HeaderRoutingMatch") + proto.RegisterType((*InfluxdbMetric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.InfluxdbMetric") proto.RegisterType((*IstioDestinationRule)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.IstioDestinationRule") proto.RegisterType((*IstioTrafficRouting)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.IstioTrafficRouting") proto.RegisterType((*IstioVirtualService)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.IstioVirtualService") @@ -2878,6 +2991,7 @@ func init() { proto.RegisterType((*KayentaMetric)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.KayentaMetric") proto.RegisterType((*KayentaScope)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.KayentaScope") proto.RegisterType((*KayentaThreshold)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.KayentaThreshold") + proto.RegisterType((*MangedRoutes)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MangedRoutes") proto.RegisterType((*Measurement)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.Measurement") proto.RegisterMapType((map[string]string)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.Measurement.MetadataEntry") proto.RegisterType((*MeasurementRetention)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.MeasurementRetention") @@ -2912,12 +3026,15 @@ func init() { proto.RegisterType((*RolloutStatus)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutStatus") proto.RegisterType((*RolloutStrategy)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutStrategy") proto.RegisterType((*RolloutTrafficRouting)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RolloutTrafficRouting") + proto.RegisterType((*RouteMatch)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch") + proto.RegisterMapType((map[string]StringMatch)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RouteMatch.HeadersEntry") proto.RegisterType((*RunSummary)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.RunSummary") proto.RegisterType((*SMITrafficRouting)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SMITrafficRouting") proto.RegisterType((*ScopeDetail)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.ScopeDetail") proto.RegisterType((*SecretKeyRef)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SecretKeyRef") proto.RegisterType((*SetCanaryScale)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetCanaryScale") - proto.RegisterType((*SetHeaderRouting)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRouting") + proto.RegisterType((*SetHeaderRoute)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetHeaderRoute") + proto.RegisterType((*SetMirrorRoute)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.SetMirrorRoute") proto.RegisterType((*StickinessConfig)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StickinessConfig") proto.RegisterType((*StringMatch)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.StringMatch") proto.RegisterType((*TLSRoute)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.TLSRoute") @@ -2938,465 +3055,479 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 7317 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x24, 0x59, - 0x75, 0xf0, 0x56, 0xb7, 0xdb, 0x6e, 0x1f, 0x7b, 0xfc, 0x73, 0xc7, 0xc3, 0x78, 0xbd, 0x3b, 0xd3, - 0x4b, 0x2d, 0xda, 0x6f, 0xf9, 0x3e, 0xf0, 0xc0, 0xfe, 0x7c, 0x59, 0x58, 0xb4, 0x49, 0xb7, 0x3d, - 0xb3, 0xe3, 0x59, 0xdb, 0xd3, 0x73, 0xdb, 0x33, 0x03, 0x0b, 0x4b, 0x28, 0x77, 0x5f, 0xb7, 0x6b, - 0xa6, 0xbb, 0xaa, 0xa9, 0xaa, 0xf6, 0x8c, 0x97, 0x15, 0xec, 0x06, 0xed, 0x06, 0x22, 0x10, 0x9b, - 0x00, 0x8a, 0xa2, 0x88, 0x08, 0x45, 0x48, 0x89, 0x02, 0x4f, 0x28, 0x51, 0x5e, 0x90, 0x12, 0x05, - 0x50, 0x88, 0xa2, 0x44, 0x24, 0x4a, 0x02, 0x44, 0xa2, 0x93, 0x35, 0x79, 0x49, 0x94, 0x28, 0x8a, - 0x44, 0x14, 0x31, 0x4f, 0xd1, 0xfd, 0xad, 0x5b, 0x3f, 0xed, 0xe9, 0x76, 0x97, 0x87, 0x55, 0xc2, - 0x5b, 0xf7, 0x39, 0xe7, 0x9e, 0x73, 0xff, 0xcf, 0xb9, 0xe7, 0x9e, 0x7b, 0x0a, 0xd6, 0x9b, 0x76, - 0xb0, 0xdb, 0xdd, 0x5e, 0xae, 0xbb, 0xed, 0x73, 0x96, 0xd7, 0x74, 0x3b, 0x9e, 0x7b, 0x83, 0xfd, - 0x78, 0xa7, 0xe7, 0xb6, 0x5a, 0x6e, 0x37, 0xf0, 0xcf, 0x75, 0x6e, 0x36, 0xcf, 0x59, 0x1d, 0xdb, - 0x3f, 0xa7, 0x20, 0x7b, 0xef, 0xb6, 0x5a, 0x9d, 0x5d, 0xeb, 0xdd, 0xe7, 0x9a, 0xc4, 0x21, 0x9e, - 0x15, 0x90, 0xc6, 0x72, 0xc7, 0x73, 0x03, 0x17, 0xbd, 0x2f, 0xe4, 0xb6, 0x2c, 0xb9, 0xb1, 0x1f, - 0xbf, 0x28, 0xcb, 0x2e, 0x77, 0x6e, 0x36, 0x97, 0x29, 0xb7, 0x65, 0x05, 0x91, 0xdc, 0x96, 0xde, - 0xa9, 0xd5, 0xa5, 0xe9, 0x36, 0xdd, 0x73, 0x8c, 0xe9, 0x76, 0x77, 0x87, 0xfd, 0x63, 0x7f, 0xd8, - 0x2f, 0x2e, 0x6c, 0xe9, 0xe1, 0x9b, 0x4f, 0xf9, 0xcb, 0xb6, 0x4b, 0xeb, 0x76, 0x6e, 0xdb, 0x0a, - 0xea, 0xbb, 0xe7, 0xf6, 0x12, 0x35, 0x5a, 0x32, 0x35, 0xa2, 0xba, 0xeb, 0x91, 0x34, 0x9a, 0x27, - 0x42, 0x9a, 0xb6, 0x55, 0xdf, 0xb5, 0x1d, 0xe2, 0xed, 0x87, 0xad, 0x6e, 0x93, 0xc0, 0x4a, 0x2b, - 0x75, 0xae, 0x5f, 0x29, 0xaf, 0xeb, 0x04, 0x76, 0x9b, 0x24, 0x0a, 0xfc, 0xff, 0xbb, 0x15, 0xf0, - 0xeb, 0xbb, 0xa4, 0x6d, 0x25, 0xca, 0x3d, 0xde, 0xaf, 0x5c, 0x37, 0xb0, 0x5b, 0xe7, 0x6c, 0x27, - 0xf0, 0x03, 0x2f, 0x5e, 0xc8, 0xfc, 0x56, 0x1e, 0x26, 0xcb, 0xeb, 0x95, 0x5a, 0x60, 0x05, 0x5d, - 0x1f, 0xbd, 0x66, 0xc0, 0x74, 0xcb, 0xb5, 0x1a, 0x15, 0xab, 0x65, 0x39, 0x75, 0xe2, 0x2d, 0x1a, - 0x0f, 0x19, 0x8f, 0x4e, 0x3d, 0xb6, 0xbe, 0x3c, 0xca, 0x78, 0x2d, 0x97, 0x6f, 0xf9, 0x98, 0xf8, - 0x6e, 0xd7, 0xab, 0x13, 0x4c, 0x76, 0x2a, 0x0b, 0xdf, 0xe9, 0x95, 0xee, 0x3b, 0xe8, 0x95, 0xa6, - 0xd7, 0x35, 0x49, 0x38, 0x22, 0x17, 0x7d, 0xd1, 0x80, 0xf9, 0xba, 0xe5, 0x58, 0xde, 0xfe, 0x96, - 0xe5, 0x35, 0x49, 0xf0, 0xac, 0xe7, 0x76, 0x3b, 0x8b, 0xb9, 0x63, 0xa8, 0xcd, 0xfd, 0xa2, 0x36, - 0xf3, 0x2b, 0x71, 0x71, 0x38, 0x59, 0x03, 0x56, 0x2f, 0x3f, 0xb0, 0xb6, 0x5b, 0x44, 0xaf, 0x57, - 0xfe, 0x38, 0xeb, 0x55, 0x8b, 0x8b, 0xc3, 0xc9, 0x1a, 0x98, 0xaf, 0xe6, 0x61, 0xbe, 0xbc, 0x5e, - 0xd9, 0xf2, 0xac, 0x9d, 0x1d, 0xbb, 0x8e, 0xdd, 0x6e, 0x60, 0x3b, 0x4d, 0xf4, 0x76, 0x98, 0xb0, - 0x9d, 0xa6, 0x47, 0x7c, 0x9f, 0x0d, 0xe4, 0x64, 0x65, 0x56, 0x30, 0x9d, 0x58, 0xe3, 0x60, 0x2c, - 0xf1, 0xe8, 0x49, 0x98, 0xf2, 0x89, 0xb7, 0x67, 0xd7, 0x49, 0xd5, 0xf5, 0x02, 0xd6, 0xd3, 0x85, - 0xca, 0x49, 0x41, 0x3e, 0x55, 0x0b, 0x51, 0x58, 0xa7, 0xa3, 0xc5, 0x3c, 0xd7, 0x0d, 0x04, 0x9e, - 0x75, 0xc4, 0x64, 0x58, 0x0c, 0x87, 0x28, 0xac, 0xd3, 0xa1, 0xd7, 0x0d, 0x98, 0xf3, 0x03, 0xbb, - 0x7e, 0xd3, 0x76, 0x88, 0xef, 0xaf, 0xb8, 0xce, 0x8e, 0xdd, 0x5c, 0x2c, 0xb0, 0x5e, 0xdc, 0x1c, - 0xad, 0x17, 0x6b, 0x31, 0xae, 0x95, 0x85, 0x83, 0x5e, 0x69, 0x2e, 0x0e, 0xc5, 0x09, 0xe9, 0x68, - 0x15, 0xe6, 0x2c, 0xc7, 0x71, 0x03, 0x2b, 0xb0, 0x5d, 0xa7, 0xea, 0x91, 0x1d, 0xfb, 0xf6, 0xe2, - 0x18, 0x6b, 0xce, 0xa2, 0x68, 0xce, 0x5c, 0x39, 0x86, 0xc7, 0x89, 0x12, 0xe6, 0x2a, 0x2c, 0x96, - 0xdb, 0xdb, 0x96, 0xef, 0x5b, 0x0d, 0xd7, 0x8b, 0x8d, 0xc6, 0xa3, 0x50, 0x6c, 0x5b, 0x9d, 0x8e, - 0xed, 0x34, 0xe9, 0x70, 0xe4, 0x1f, 0x9d, 0xac, 0x4c, 0x1f, 0xf4, 0x4a, 0xc5, 0x0d, 0x01, 0xc3, - 0x0a, 0x6b, 0xfe, 0x20, 0x07, 0x53, 0x65, 0xc7, 0x6a, 0xed, 0xfb, 0xb6, 0x8f, 0xbb, 0x0e, 0xfa, - 0x08, 0x14, 0xe9, 0xee, 0xd2, 0xb0, 0x02, 0x4b, 0xac, 0xc8, 0x77, 0x2d, 0xf3, 0xc5, 0xbe, 0xac, - 0x2f, 0xf6, 0xb0, 0x5f, 0x28, 0xf5, 0xf2, 0xde, 0xbb, 0x97, 0x2f, 0x6f, 0xdf, 0x20, 0xf5, 0x60, - 0x83, 0x04, 0x56, 0x05, 0x89, 0x56, 0x40, 0x08, 0xc3, 0x8a, 0x2b, 0x72, 0x61, 0xcc, 0xef, 0x90, - 0xba, 0x58, 0x61, 0x1b, 0x23, 0xce, 0xe4, 0xb0, 0xea, 0xb5, 0x0e, 0xa9, 0x57, 0xa6, 0x85, 0xe8, - 0x31, 0xfa, 0x0f, 0x33, 0x41, 0xe8, 0x16, 0x8c, 0xfb, 0x6c, 0xcf, 0x11, 0x8b, 0xe7, 0x72, 0x76, - 0x22, 0x19, 0xdb, 0xca, 0x8c, 0x10, 0x3a, 0xce, 0xff, 0x63, 0x21, 0xce, 0xfc, 0x7b, 0x03, 0x4e, - 0x6a, 0xd4, 0x65, 0xaf, 0xd9, 0x6d, 0x13, 0x27, 0x40, 0x0f, 0xc1, 0x98, 0x63, 0xb5, 0x89, 0x58, - 0x28, 0xaa, 0xca, 0x9b, 0x56, 0x9b, 0x60, 0x86, 0x41, 0x0f, 0x43, 0x61, 0xcf, 0x6a, 0x75, 0x09, - 0xeb, 0xa4, 0xc9, 0xca, 0x09, 0x41, 0x52, 0xb8, 0x46, 0x81, 0x98, 0xe3, 0xd0, 0x4b, 0x30, 0xc9, - 0x7e, 0x5c, 0xf0, 0xdc, 0x76, 0x46, 0x4d, 0x13, 0x35, 0xbc, 0x26, 0xd9, 0x56, 0x4e, 0x1c, 0xf4, - 0x4a, 0x93, 0xea, 0x2f, 0x0e, 0x05, 0x9a, 0xff, 0x60, 0xc0, 0xac, 0xd6, 0xb8, 0x75, 0xdb, 0x0f, - 0xd0, 0x87, 0x12, 0x93, 0x67, 0x79, 0xb0, 0xc9, 0x43, 0x4b, 0xb3, 0xa9, 0x33, 0x27, 0x5a, 0x5a, - 0x94, 0x10, 0x6d, 0xe2, 0x38, 0x50, 0xb0, 0x03, 0xd2, 0xf6, 0x17, 0x73, 0x0f, 0xe5, 0x1f, 0x9d, - 0x7a, 0x6c, 0x2d, 0xb3, 0x61, 0x0c, 0xfb, 0x77, 0x8d, 0xf2, 0xc7, 0x5c, 0x8c, 0xf9, 0xf5, 0xb1, - 0x48, 0x0b, 0xe9, 0x8c, 0x42, 0x2e, 0x4c, 0xb4, 0x49, 0xe0, 0xd9, 0x75, 0xbe, 0xae, 0xa6, 0x1e, - 0x5b, 0x1d, 0xad, 0x16, 0x1b, 0x8c, 0x59, 0xb8, 0x59, 0xf2, 0xff, 0x3e, 0x96, 0x52, 0xd0, 0x2e, - 0x8c, 0x59, 0x5e, 0x53, 0xb6, 0xf9, 0x42, 0x36, 0xe3, 0x1b, 0xce, 0xb9, 0xb2, 0xd7, 0xf4, 0x31, - 0x93, 0x80, 0xce, 0xc1, 0x64, 0x40, 0xbc, 0xb6, 0xed, 0x58, 0x01, 0xdf, 0x5d, 0x8b, 0x95, 0x79, - 0x41, 0x36, 0xb9, 0x25, 0x11, 0x38, 0xa4, 0x41, 0x2d, 0x18, 0x6f, 0x78, 0xfb, 0xb8, 0xeb, 0x2c, - 0x8e, 0x65, 0xd1, 0x15, 0xab, 0x8c, 0x57, 0xb8, 0x98, 0xf8, 0x7f, 0x2c, 0x64, 0xa0, 0xaf, 0x18, - 0xb0, 0xd0, 0x26, 0x96, 0xdf, 0xf5, 0x08, 0x6d, 0x02, 0x26, 0x01, 0x71, 0xe8, 0x6e, 0xb8, 0x58, - 0x60, 0xc2, 0xf1, 0xa8, 0xe3, 0x90, 0xe4, 0x5c, 0x79, 0x50, 0x54, 0x65, 0x21, 0x0d, 0x8b, 0x53, - 0x6b, 0x63, 0xfe, 0x60, 0x0c, 0xe6, 0x13, 0x3b, 0x04, 0x7a, 0x02, 0x0a, 0x9d, 0x5d, 0xcb, 0x97, - 0x4b, 0xfe, 0xac, 0x9c, 0x6f, 0x55, 0x0a, 0xbc, 0xd3, 0x2b, 0x9d, 0x90, 0x45, 0x18, 0x00, 0x73, - 0x62, 0xaa, 0x53, 0xdb, 0xc4, 0xf7, 0xad, 0xa6, 0xdc, 0x07, 0xb4, 0x69, 0xc2, 0xc0, 0x58, 0xe2, - 0xd1, 0x2f, 0x1b, 0x70, 0x82, 0x4f, 0x19, 0x4c, 0xfc, 0x6e, 0x2b, 0xa0, 0x7b, 0x1d, 0xed, 0x96, - 0x4b, 0x59, 0x4c, 0x4f, 0xce, 0xb2, 0x72, 0x4a, 0x48, 0x3f, 0xa1, 0x43, 0x7d, 0x1c, 0x95, 0x8b, - 0xae, 0xc3, 0xa4, 0x1f, 0x58, 0x5e, 0x40, 0x1a, 0xe5, 0x80, 0x69, 0xb5, 0xa9, 0xc7, 0xfe, 0xef, - 0x60, 0x9b, 0xc0, 0x96, 0xdd, 0x26, 0x7c, 0xc3, 0xa9, 0x49, 0x06, 0x38, 0xe4, 0x85, 0x5e, 0x02, - 0xf0, 0xba, 0x4e, 0xad, 0xdb, 0x6e, 0x5b, 0xde, 0xbe, 0xd0, 0xe0, 0x17, 0x47, 0x6b, 0x1e, 0x56, - 0xfc, 0x42, 0x9d, 0x15, 0xc2, 0xb0, 0x26, 0x0f, 0xbd, 0x62, 0xc0, 0x09, 0x3e, 0x13, 0x65, 0x0d, - 0xc6, 0x33, 0xae, 0xc1, 0x3c, 0xed, 0xda, 0x55, 0x5d, 0x04, 0x8e, 0x4a, 0x34, 0xff, 0x36, 0xaa, - 0x4f, 0x6a, 0x01, 0xb5, 0xae, 0x9b, 0xfb, 0xe8, 0x83, 0x70, 0xbf, 0xdf, 0xad, 0xd7, 0x89, 0xef, - 0xef, 0x74, 0x5b, 0xb8, 0xeb, 0x5c, 0xb4, 0xfd, 0xc0, 0xf5, 0xf6, 0xd7, 0xed, 0xb6, 0x1d, 0xb0, - 0x19, 0x57, 0xa8, 0x9c, 0x39, 0xe8, 0x95, 0xee, 0xaf, 0xf5, 0x23, 0xc2, 0xfd, 0xcb, 0x23, 0x0b, - 0x1e, 0xe8, 0x3a, 0xfd, 0xd9, 0x73, 0xeb, 0xad, 0x74, 0xd0, 0x2b, 0x3d, 0x70, 0xb5, 0x3f, 0x19, - 0x3e, 0x8c, 0x87, 0xf9, 0x2f, 0x06, 0xcc, 0xc9, 0x76, 0x6d, 0x91, 0x76, 0xa7, 0x45, 0x77, 0x97, - 0xe3, 0x37, 0x44, 0x82, 0x88, 0x21, 0x82, 0xb3, 0x51, 0x27, 0xb2, 0xfe, 0xfd, 0xac, 0x11, 0xf3, - 0x9f, 0x0d, 0x58, 0x88, 0x13, 0xdf, 0x03, 0xe5, 0xe9, 0x47, 0x95, 0xe7, 0x66, 0xb6, 0xad, 0xed, - 0xa3, 0x41, 0x5f, 0x1b, 0x4b, 0xb6, 0xf5, 0x7f, 0xba, 0x1a, 0x0d, 0xb5, 0x62, 0xfe, 0xa7, 0xa9, - 0x15, 0xc7, 0xde, 0x54, 0x5a, 0xf1, 0x77, 0xc7, 0x60, 0xba, 0xec, 0x04, 0x76, 0x79, 0x67, 0xc7, - 0x76, 0xec, 0x60, 0x1f, 0x7d, 0x26, 0x07, 0xe7, 0x3a, 0x1e, 0xd9, 0x21, 0x9e, 0x47, 0x1a, 0xab, - 0x5d, 0xcf, 0x76, 0x9a, 0xb5, 0xfa, 0x2e, 0x69, 0x74, 0x5b, 0xb6, 0xd3, 0x5c, 0x6b, 0x3a, 0xae, - 0x02, 0x9f, 0xbf, 0x4d, 0xea, 0x5d, 0xd6, 0x24, 0xbe, 0x28, 0xda, 0xa3, 0x35, 0xa9, 0x3a, 0x9c, - 0xd0, 0xca, 0xe3, 0x07, 0xbd, 0xd2, 0xb9, 0x21, 0x0b, 0xe1, 0x61, 0x9b, 0x86, 0x3e, 0x95, 0x83, - 0x65, 0x8f, 0x7c, 0xb4, 0x6b, 0x0f, 0xde, 0x1b, 0x7c, 0xd7, 0x6a, 0x8d, 0xa8, 0x7e, 0x86, 0x92, - 0x59, 0x79, 0xec, 0xa0, 0x57, 0x1a, 0xb2, 0x0c, 0x1e, 0xb2, 0x5d, 0xe6, 0x37, 0x73, 0x70, 0xaa, - 0xdc, 0xe9, 0x6c, 0x10, 0x7f, 0x37, 0x76, 0xa8, 0xfd, 0x9c, 0x01, 0x33, 0x7b, 0xb6, 0x17, 0x74, - 0xad, 0x96, 0x74, 0x02, 0xf0, 0x29, 0x51, 0x1b, 0x71, 0x39, 0x73, 0x69, 0xd7, 0x22, 0xac, 0x2b, - 0xe8, 0xa0, 0x57, 0x9a, 0x89, 0xc2, 0x70, 0x4c, 0x3c, 0xfa, 0x75, 0x03, 0xe6, 0x04, 0x68, 0xd3, - 0x6d, 0x10, 0xdd, 0x73, 0x74, 0x35, 0xcb, 0x3a, 0x29, 0xe6, 0xdc, 0xc5, 0x10, 0x87, 0xe2, 0x44, - 0x25, 0xcc, 0x7f, 0xcb, 0xc1, 0xe9, 0x3e, 0x3c, 0xd0, 0xef, 0x18, 0xb0, 0xc0, 0xdd, 0x4d, 0x1a, - 0x0a, 0x93, 0x1d, 0xd1, 0x9b, 0x1f, 0xc8, 0xba, 0xe6, 0x98, 0xae, 0x05, 0xe2, 0xd4, 0x49, 0x65, - 0x91, 0x6e, 0x1b, 0x2b, 0x29, 0xa2, 0x71, 0x6a, 0x85, 0x58, 0x4d, 0xb9, 0x03, 0x2a, 0x56, 0xd3, - 0xdc, 0x3d, 0xa9, 0x69, 0x2d, 0x45, 0x34, 0x4e, 0xad, 0x90, 0xf9, 0xf3, 0xf0, 0xc0, 0x21, 0xec, - 0xee, 0x7e, 0xe2, 0x37, 0x5f, 0x50, 0xb3, 0x3e, 0x3a, 0xe7, 0x06, 0x70, 0x16, 0x98, 0x30, 0xee, - 0xb9, 0xdd, 0x80, 0x70, 0xed, 0x36, 0x59, 0x01, 0xaa, 0x27, 0x30, 0x83, 0x60, 0x81, 0x31, 0xbf, - 0x69, 0x40, 0x71, 0x08, 0xff, 0x43, 0x29, 0xea, 0x7f, 0x98, 0x4c, 0xf8, 0x1e, 0x82, 0xa4, 0xef, - 0xe1, 0xd9, 0xd1, 0x46, 0x63, 0x10, 0x9f, 0xc3, 0xbf, 0x1b, 0x30, 0x9f, 0xf0, 0x51, 0xa0, 0x5d, - 0x58, 0xe8, 0xb8, 0x0d, 0x69, 0x5f, 0x5c, 0xb4, 0xfc, 0x5d, 0x86, 0x13, 0xcd, 0x7b, 0x82, 0x8e, - 0x64, 0x35, 0x05, 0x7f, 0xa7, 0x57, 0x5a, 0x54, 0x4c, 0x62, 0x04, 0x38, 0x95, 0x23, 0xea, 0x40, - 0x71, 0xc7, 0x26, 0xad, 0x46, 0x38, 0x05, 0x47, 0xb4, 0x24, 0x2e, 0x08, 0x6e, 0xdc, 0x3d, 0x27, - 0xff, 0x61, 0x25, 0xc5, 0xbc, 0x02, 0x33, 0x51, 0x67, 0xed, 0x00, 0x83, 0x77, 0x06, 0xf2, 0x96, - 0xe7, 0x88, 0xa1, 0x9b, 0x12, 0x04, 0xf9, 0x32, 0xde, 0xc4, 0x14, 0x6e, 0xfe, 0x64, 0x0c, 0x66, - 0x2b, 0xad, 0x2e, 0x79, 0xd6, 0x23, 0x44, 0x9e, 0x4f, 0xcb, 0x30, 0xdb, 0xf1, 0xc8, 0x9e, 0x4d, - 0x6e, 0xd5, 0x48, 0x8b, 0xd4, 0x03, 0xd7, 0x13, 0xfc, 0x4f, 0x8b, 0xe2, 0xb3, 0xd5, 0x28, 0x1a, - 0xc7, 0xe9, 0xd1, 0x33, 0x30, 0x63, 0xd5, 0x03, 0x7b, 0x8f, 0x28, 0x0e, 0xbc, 0x02, 0x6f, 0x11, - 0x1c, 0x66, 0xca, 0x11, 0x2c, 0x8e, 0x51, 0xa3, 0x0f, 0xc1, 0xa2, 0x5f, 0xb7, 0x5a, 0xe4, 0x6a, - 0x47, 0x88, 0x5a, 0xd9, 0x25, 0xf5, 0x9b, 0x55, 0xd7, 0x76, 0x02, 0xe1, 0x8d, 0x78, 0x48, 0x70, - 0x5a, 0xac, 0xf5, 0xa1, 0xc3, 0x7d, 0x39, 0xa0, 0x3f, 0x32, 0xe0, 0x4c, 0xc7, 0x23, 0x55, 0xcf, - 0x6d, 0xbb, 0x54, 0xcd, 0x24, 0x8e, 0xe8, 0xe2, 0xa8, 0x7a, 0x6d, 0x44, 0x7d, 0xca, 0x21, 0x49, - 0x17, 0xe1, 0x5b, 0x0f, 0x7a, 0xa5, 0x33, 0xd5, 0xc3, 0x2a, 0x80, 0x0f, 0xaf, 0x1f, 0xfa, 0x13, - 0x03, 0xce, 0x76, 0x5c, 0x3f, 0x38, 0xa4, 0x09, 0x85, 0x63, 0x6d, 0x82, 0x79, 0xd0, 0x2b, 0x9d, - 0xad, 0x1e, 0x5a, 0x03, 0x7c, 0x97, 0x1a, 0x9a, 0x07, 0x53, 0x30, 0xaf, 0xcd, 0x3d, 0x71, 0x7e, - 0x7d, 0x1a, 0x4e, 0xc8, 0xc9, 0x10, 0xaa, 0xf5, 0xc9, 0xd0, 0xdf, 0x50, 0xd6, 0x91, 0x38, 0x4a, - 0x4b, 0xe7, 0x9d, 0x9a, 0x8a, 0xbc, 0x74, 0x6c, 0xde, 0x55, 0x23, 0x58, 0x1c, 0xa3, 0x46, 0x6b, - 0x70, 0x52, 0x40, 0x30, 0xe9, 0xb4, 0xec, 0xba, 0xb5, 0xe2, 0x76, 0xc5, 0x94, 0x2b, 0x54, 0x4e, - 0x1f, 0xf4, 0x4a, 0x27, 0xab, 0x49, 0x34, 0x4e, 0x2b, 0x83, 0xd6, 0x61, 0xc1, 0xea, 0x06, 0xae, - 0x6a, 0xff, 0x79, 0x87, 0x6a, 0x8a, 0x06, 0x9b, 0x5a, 0x45, 0xae, 0x52, 0xca, 0x29, 0x78, 0x9c, - 0x5a, 0x0a, 0x55, 0x63, 0xdc, 0x6a, 0xa4, 0xee, 0x3a, 0x0d, 0x3e, 0xca, 0x85, 0xd0, 0x0a, 0x2f, - 0xa7, 0xd0, 0xe0, 0xd4, 0x92, 0xa8, 0x05, 0x33, 0x6d, 0xeb, 0xf6, 0x55, 0xc7, 0xda, 0xb3, 0xec, - 0x16, 0x15, 0x22, 0x7c, 0x18, 0xfd, 0x0f, 0xd6, 0xdd, 0xc0, 0x6e, 0x2d, 0xf3, 0xeb, 0xbc, 0xe5, - 0x35, 0x27, 0xb8, 0xec, 0xd5, 0x02, 0x6a, 0xad, 0x71, 0xe3, 0x68, 0x23, 0xc2, 0x0b, 0xc7, 0x78, - 0xa3, 0xcb, 0x70, 0x8a, 0x2d, 0xc7, 0x55, 0xf7, 0x96, 0xb3, 0x4a, 0x5a, 0xd6, 0xbe, 0x6c, 0xc0, - 0x04, 0x6b, 0xc0, 0xfd, 0x07, 0xbd, 0xd2, 0xa9, 0x5a, 0x1a, 0x01, 0x4e, 0x2f, 0x87, 0x2c, 0x78, - 0x20, 0x8a, 0xc0, 0x64, 0xcf, 0xf6, 0x6d, 0xd7, 0xe1, 0x9e, 0x88, 0x62, 0xe8, 0x89, 0xa8, 0xf5, - 0x27, 0xc3, 0x87, 0xf1, 0x40, 0xbf, 0x69, 0xc0, 0x42, 0xda, 0x32, 0x5c, 0x9c, 0xcc, 0xe2, 0xb2, - 0x22, 0xb6, 0xb4, 0xf8, 0x8c, 0x48, 0xdd, 0x14, 0x52, 0x2b, 0x81, 0x5e, 0x36, 0x60, 0xda, 0xd2, - 0x4e, 0x51, 0x8b, 0xc0, 0x6a, 0x75, 0x69, 0xd4, 0xb3, 0x7c, 0xc8, 0xb1, 0x32, 0x77, 0xd0, 0x2b, - 0x45, 0x4e, 0x6a, 0x38, 0x22, 0x11, 0xfd, 0x96, 0x01, 0xa7, 0x52, 0xd7, 0xf8, 0xe2, 0xd4, 0x71, - 0xf4, 0x10, 0x9b, 0x24, 0xe9, 0x7b, 0x4e, 0x7a, 0x35, 0xd0, 0xeb, 0x86, 0x52, 0x65, 0x1b, 0xd2, - 0x9b, 0x32, 0xcd, 0xaa, 0x76, 0x65, 0xc4, 0x83, 0x63, 0x68, 0x10, 0x48, 0xc6, 0x95, 0x93, 0x9a, - 0x66, 0x94, 0x40, 0x1c, 0x17, 0x8f, 0x3e, 0x6b, 0x48, 0xd5, 0xa8, 0x6a, 0x74, 0xe2, 0xb8, 0x6a, - 0x84, 0x42, 0x4d, 0xab, 0x2a, 0x14, 0x13, 0x8e, 0x3e, 0x0c, 0x4b, 0xd6, 0xb6, 0xeb, 0x05, 0xa9, - 0x8b, 0x6f, 0x71, 0x86, 0x2d, 0xa3, 0xb3, 0x07, 0xbd, 0xd2, 0x52, 0xb9, 0x2f, 0x15, 0x3e, 0x84, - 0x83, 0xf9, 0xb5, 0x02, 0x4c, 0x73, 0x23, 0x5f, 0xa8, 0xae, 0x6f, 0x18, 0xf0, 0x60, 0xbd, 0xeb, - 0x79, 0xc4, 0x09, 0x6a, 0x01, 0xe9, 0x24, 0x15, 0x97, 0x71, 0xac, 0x8a, 0xeb, 0xa1, 0x83, 0x5e, - 0xe9, 0xc1, 0x95, 0x43, 0xe4, 0xe3, 0x43, 0x6b, 0x87, 0xfe, 0xd2, 0x00, 0x53, 0x10, 0x54, 0xac, - 0xfa, 0xcd, 0xa6, 0xe7, 0x76, 0x9d, 0x46, 0xb2, 0x11, 0xb9, 0x63, 0x6d, 0xc4, 0x23, 0x07, 0xbd, - 0x92, 0xb9, 0x72, 0xd7, 0x5a, 0xe0, 0x01, 0x6a, 0x8a, 0x9e, 0x85, 0x79, 0x41, 0x75, 0xfe, 0x76, - 0x87, 0x78, 0x36, 0x35, 0xa7, 0xc5, 0x7d, 0x7a, 0x18, 0xa2, 0x10, 0x27, 0xc0, 0xc9, 0x32, 0xc8, - 0x87, 0x89, 0x5b, 0xc4, 0x6e, 0xee, 0x06, 0xd2, 0x7c, 0x1a, 0x31, 0x2e, 0x41, 0x1c, 0xf8, 0xaf, - 0x73, 0x9e, 0x95, 0xa9, 0x83, 0x5e, 0x69, 0x42, 0xfc, 0xc1, 0x52, 0x12, 0xda, 0x84, 0x19, 0x7e, - 0x04, 0xab, 0xda, 0x4e, 0xb3, 0xea, 0x3a, 0xfc, 0x36, 0x7f, 0xb2, 0xf2, 0x88, 0x54, 0xf8, 0xb5, - 0x08, 0xf6, 0x4e, 0xaf, 0x34, 0x2d, 0x7f, 0x6f, 0xed, 0x77, 0x08, 0x8e, 0x95, 0x36, 0xbf, 0x59, - 0x00, 0x90, 0xd3, 0x95, 0x74, 0xd0, 0xff, 0x83, 0x49, 0x9f, 0x04, 0x5c, 0xaa, 0x70, 0x9e, 0xf3, - 0x3b, 0x09, 0x09, 0xc4, 0x21, 0x1e, 0xdd, 0x84, 0x42, 0xc7, 0xea, 0xfa, 0x44, 0x0c, 0xfe, 0xa5, - 0x4c, 0x06, 0xbf, 0x4a, 0x39, 0xf2, 0x33, 0x17, 0xfb, 0x89, 0xb9, 0x0c, 0xf4, 0x49, 0x03, 0x80, - 0x44, 0x07, 0x6c, 0x64, 0xdf, 0x87, 0x10, 0x19, 0x8e, 0x29, 0xed, 0x83, 0xca, 0xcc, 0x41, 0xaf, - 0x04, 0xda, 0xd0, 0x6b, 0x62, 0xd1, 0x2d, 0x28, 0x5a, 0x72, 0xcf, 0x1f, 0x3b, 0x8e, 0x3d, 0x9f, - 0x1d, 0x85, 0xd4, 0xa4, 0x55, 0xc2, 0xd0, 0xa7, 0x0c, 0x98, 0xf1, 0x49, 0x20, 0x86, 0x8a, 0xee, - 0x3c, 0xc2, 0xe0, 0x1d, 0x71, 0xd2, 0xd5, 0x22, 0x3c, 0xf9, 0x0e, 0x1a, 0x85, 0xe1, 0x98, 0x5c, - 0x1e, 0x53, 0x42, 0x82, 0x8b, 0xc4, 0x6a, 0x10, 0x4f, 0xb8, 0xa7, 0x84, 0x2d, 0xb5, 0x39, 0x72, - 0x65, 0x22, 0x5c, 0x45, 0x4c, 0x49, 0x0c, 0x8a, 0x13, 0xd2, 0xcd, 0xbf, 0x9e, 0x86, 0x19, 0x39, - 0x8b, 0x43, 0xb3, 0x9a, 0x7b, 0x55, 0xfa, 0x98, 0xd5, 0x2b, 0x3a, 0x12, 0x47, 0x69, 0x69, 0x61, - 0xbe, 0x4e, 0xa2, 0x56, 0xb5, 0x2a, 0x5c, 0xd3, 0x91, 0x38, 0x4a, 0x8b, 0xda, 0x50, 0xf0, 0x03, - 0xd2, 0x91, 0x97, 0x90, 0x23, 0xde, 0x91, 0x85, 0x8b, 0x33, 0xbc, 0x66, 0xa0, 0xff, 0x7c, 0xcc, - 0xa5, 0x30, 0xc7, 0x60, 0x10, 0xf1, 0x15, 0x8a, 0x99, 0x99, 0xcd, 0xe2, 0x88, 0xba, 0x21, 0xf9, - 0x04, 0x89, 0xc2, 0x70, 0x4c, 0x7c, 0x8a, 0xa5, 0x5d, 0x38, 0x46, 0x4b, 0xfb, 0x79, 0x28, 0xb6, - 0xad, 0xdb, 0xb5, 0xae, 0xd7, 0x3c, 0xba, 0x45, 0x2f, 0xe2, 0x83, 0x38, 0x17, 0xac, 0xf8, 0xa1, - 0x57, 0x0c, 0x6d, 0xbd, 0x4f, 0x30, 0xe6, 0xd7, 0xb3, 0x5d, 0xef, 0x4a, 0x51, 0xf5, 0x5d, 0xf9, - 0x09, 0xbb, 0xb7, 0x78, 0xcf, 0xed, 0x5e, 0x6a, 0xc3, 0xf1, 0x05, 0xa2, 0x6c, 0xb8, 0xc9, 0x63, - 0xb5, 0xe1, 0x56, 0x22, 0xc2, 0x70, 0x4c, 0x38, 0xab, 0x0f, 0x5f, 0x73, 0xaa, 0x3e, 0x70, 0xac, - 0xf5, 0xa9, 0x45, 0x84, 0xe1, 0x98, 0xf0, 0xfe, 0x87, 0xbd, 0xa9, 0xe3, 0x39, 0xec, 0x4d, 0x67, - 0x70, 0xd8, 0x3b, 0xdc, 0x0e, 0x3e, 0x31, 0xaa, 0x1d, 0x8c, 0x2e, 0x01, 0x6a, 0xec, 0x3b, 0x56, - 0xdb, 0xae, 0x8b, 0xcd, 0x92, 0xe9, 0xac, 0x19, 0xe6, 0x0c, 0x58, 0x12, 0x1b, 0x19, 0x5a, 0x4d, - 0x50, 0xe0, 0x94, 0x52, 0x28, 0x80, 0x62, 0x47, 0x9a, 0x3b, 0xb3, 0x59, 0xcc, 0x7e, 0x69, 0xfe, - 0xf0, 0x7b, 0x6a, 0xba, 0xf0, 0x24, 0x04, 0x2b, 0x49, 0xe6, 0x7f, 0x1a, 0x30, 0xb7, 0xd2, 0x72, - 0xbb, 0x8d, 0xeb, 0x56, 0x50, 0xdf, 0xe5, 0x97, 0xaa, 0xe8, 0x19, 0x28, 0xda, 0x4e, 0x40, 0xbc, - 0x3d, 0xab, 0x25, 0x34, 0x8a, 0x29, 0xef, 0x9d, 0xd7, 0x04, 0xfc, 0x4e, 0xaf, 0x34, 0xb3, 0xda, - 0xf5, 0x58, 0xb4, 0x22, 0xdf, 0x5f, 0xb0, 0x2a, 0x83, 0xbe, 0x6c, 0xc0, 0x3c, 0xbf, 0x96, 0x5d, - 0xb5, 0x02, 0xeb, 0x4a, 0x97, 0x78, 0x36, 0x91, 0x17, 0xb3, 0x23, 0x6e, 0x2d, 0xf1, 0xba, 0x4a, - 0x01, 0xfb, 0xa1, 0x5d, 0xbb, 0x11, 0x97, 0x8c, 0x93, 0x95, 0x31, 0x3f, 0x9f, 0x87, 0xfb, 0xfb, - 0xf2, 0x42, 0x4b, 0x90, 0xb3, 0x1b, 0xa2, 0xe9, 0x20, 0xf8, 0xe6, 0xd6, 0x1a, 0x38, 0x67, 0x37, - 0xd0, 0x32, 0x33, 0xd1, 0x3c, 0xe2, 0xfb, 0xf2, 0x8e, 0x6e, 0x52, 0x59, 0x53, 0x02, 0x8a, 0x35, - 0x0a, 0x54, 0x82, 0x42, 0xcb, 0xda, 0x26, 0x2d, 0x61, 0x7e, 0x33, 0xa3, 0x6f, 0x9d, 0x02, 0x30, - 0x87, 0xa3, 0x5f, 0x32, 0x00, 0x78, 0x05, 0xa9, 0xf1, 0x2e, 0xf4, 0x1a, 0xce, 0xb6, 0x9b, 0x28, - 0x67, 0x5e, 0xcb, 0xf0, 0x3f, 0xd6, 0xa4, 0xa2, 0x2d, 0x18, 0xa7, 0xf6, 0x9f, 0xdb, 0x38, 0xb2, - 0x1a, 0x63, 0x77, 0x12, 0x55, 0xc6, 0x03, 0x0b, 0x5e, 0xb4, 0xaf, 0x3c, 0x12, 0x74, 0x3d, 0x87, - 0x76, 0x2d, 0x53, 0x5c, 0x45, 0x5e, 0x0b, 0xac, 0xa0, 0x58, 0xa3, 0x30, 0xff, 0x30, 0x07, 0x0b, - 0x69, 0x55, 0xa7, 0xfa, 0x61, 0x9c, 0xd7, 0x56, 0x9c, 0x24, 0xdf, 0x9f, 0x7d, 0xff, 0x88, 0x08, - 0x03, 0x75, 0x0f, 0x2f, 0x62, 0xa0, 0x84, 0x5c, 0xf4, 0x7e, 0xd5, 0x43, 0xb9, 0x23, 0xf6, 0x90, - 0xe2, 0x1c, 0xeb, 0xa5, 0x87, 0x60, 0xcc, 0xa7, 0x23, 0x9f, 0x8f, 0xfa, 0xfb, 0xd9, 0x18, 0x31, - 0x0c, 0xa5, 0xe8, 0x3a, 0x76, 0x20, 0x42, 0x88, 0x15, 0xc5, 0x55, 0xc7, 0x0e, 0x30, 0xc3, 0x98, - 0x5f, 0xcc, 0xc1, 0x52, 0xff, 0x46, 0xa1, 0x2f, 0x1a, 0x00, 0x0d, 0x6a, 0xdd, 0xd3, 0x29, 0x29, - 0x23, 0x32, 0xac, 0xe3, 0xea, 0xc3, 0x55, 0x29, 0x29, 0x0c, 0xcf, 0x51, 0x20, 0x1f, 0x6b, 0x15, - 0x41, 0x8f, 0xc9, 0xa9, 0xbf, 0x69, 0xb5, 0xa5, 0x01, 0xaa, 0xca, 0x6c, 0x28, 0x0c, 0xd6, 0xa8, - 0xe8, 0xf1, 0xcd, 0xb1, 0xda, 0xc4, 0xef, 0x58, 0x2a, 0x46, 0x9c, 0x1d, 0xdf, 0x36, 0x25, 0x10, - 0x87, 0x78, 0xb3, 0x05, 0x0f, 0x0f, 0x50, 0xcf, 0x8c, 0xe2, 0x75, 0xcd, 0xff, 0x30, 0xe0, 0xf4, - 0x4a, 0xab, 0xeb, 0x07, 0xc4, 0xfb, 0x5f, 0x13, 0xed, 0xf4, 0x5f, 0x06, 0x3c, 0xd0, 0xa7, 0xcd, - 0xf7, 0x20, 0xe8, 0xe9, 0xc5, 0x68, 0xd0, 0xd3, 0xd5, 0x51, 0xa7, 0x74, 0x6a, 0x3b, 0xfa, 0xc4, - 0x3e, 0x05, 0x70, 0x82, 0xee, 0x5a, 0x0d, 0xb7, 0x99, 0x91, 0xde, 0x7c, 0x18, 0x0a, 0x1f, 0xa5, - 0xfa, 0x27, 0x3e, 0xc7, 0x98, 0x52, 0xc2, 0x1c, 0x67, 0xbe, 0x0f, 0x44, 0x84, 0x50, 0x6c, 0xf1, - 0x18, 0x83, 0x2c, 0x1e, 0xf3, 0xef, 0x72, 0xa0, 0x1d, 0xfb, 0xef, 0xc1, 0xa4, 0x74, 0x22, 0x93, - 0x72, 0xc4, 0x83, 0xbc, 0xe6, 0xc4, 0xe8, 0xf7, 0x14, 0x60, 0x2f, 0xf6, 0x14, 0x60, 0x33, 0x33, - 0x89, 0x87, 0xbf, 0x04, 0xf8, 0x9e, 0x01, 0x0f, 0x84, 0xc4, 0x49, 0x8f, 0xdc, 0xdd, 0x77, 0x98, - 0x27, 0x61, 0xca, 0x0a, 0x8b, 0x89, 0x39, 0xa0, 0x5e, 0xbf, 0x68, 0x1c, 0xb1, 0x4e, 0x17, 0x06, - 0x1e, 0xe7, 0x8f, 0x18, 0x78, 0x3c, 0x76, 0x78, 0xe0, 0xb1, 0xf9, 0xe3, 0x1c, 0x9c, 0x49, 0xb6, - 0x4c, 0xae, 0x8d, 0xc1, 0x2e, 0xac, 0x9f, 0x82, 0xe9, 0x40, 0x14, 0xd0, 0x76, 0x7a, 0xf5, 0x76, - 0x6b, 0x4b, 0xc3, 0xe1, 0x08, 0x25, 0x2d, 0x59, 0xe7, 0xab, 0xb2, 0x56, 0x77, 0x3b, 0x32, 0x6c, - 0x5d, 0x95, 0x5c, 0xd1, 0x70, 0x38, 0x42, 0xa9, 0x02, 0x02, 0xc7, 0x8e, 0x3d, 0x20, 0xb0, 0x06, - 0xa7, 0x64, 0x08, 0xd4, 0x05, 0xd7, 0x5b, 0x71, 0xdb, 0x9d, 0x16, 0x11, 0x81, 0xeb, 0xb4, 0xb2, - 0x67, 0x44, 0x91, 0x53, 0x38, 0x8d, 0x08, 0xa7, 0x97, 0x35, 0xbf, 0x97, 0x87, 0x93, 0x61, 0xb7, - 0xaf, 0xb8, 0x4e, 0xc3, 0x66, 0x81, 0x64, 0x4f, 0xc3, 0x58, 0xb0, 0xdf, 0x91, 0x9d, 0xfd, 0x7f, - 0x64, 0x75, 0xb6, 0xf6, 0x3b, 0x74, 0xb4, 0x4f, 0xa7, 0x14, 0x61, 0x3e, 0x51, 0x56, 0x08, 0xad, - 0xab, 0xd5, 0xc1, 0x47, 0xe0, 0x89, 0xe8, 0x6c, 0xbe, 0xd3, 0x2b, 0xa5, 0x3c, 0x5d, 0x5c, 0x56, - 0x9c, 0xa2, 0x73, 0x1e, 0xdd, 0x80, 0x99, 0x96, 0xe5, 0x07, 0x57, 0x3b, 0x0d, 0x2b, 0x20, 0x5b, - 0x76, 0x9b, 0x88, 0x35, 0x37, 0x4c, 0x34, 0xb8, 0xba, 0xc4, 0x5d, 0x8f, 0x70, 0xc2, 0x31, 0xce, - 0x68, 0x0f, 0x10, 0x85, 0x6c, 0x79, 0x96, 0xe3, 0xf3, 0x56, 0x51, 0x79, 0xc3, 0x47, 0x9f, 0xab, - 0x63, 0xd9, 0x7a, 0x82, 0x1b, 0x4e, 0x91, 0x80, 0x1e, 0x81, 0x71, 0x8f, 0x58, 0xbe, 0x18, 0xcc, - 0xc9, 0x70, 0xfd, 0x63, 0x06, 0xc5, 0x02, 0xab, 0x2f, 0xa8, 0xf1, 0xbb, 0x2c, 0xa8, 0x1f, 0x1a, - 0x30, 0x13, 0x0e, 0xd3, 0x3d, 0x50, 0x92, 0xed, 0xa8, 0x92, 0xbc, 0x98, 0xd5, 0x96, 0xd8, 0x47, - 0x2f, 0xfe, 0xe9, 0xb8, 0xde, 0x3e, 0x16, 0x0d, 0xfc, 0x31, 0x98, 0x94, 0xab, 0x5a, 0x5a, 0x9f, - 0x23, 0x9e, 0x6e, 0x23, 0x76, 0x89, 0xf6, 0x8a, 0x45, 0x08, 0xc1, 0xa1, 0x3c, 0xaa, 0x96, 0x1b, - 0x42, 0xe5, 0x8a, 0x69, 0xaf, 0xd4, 0xb2, 0x54, 0xc5, 0x69, 0x6a, 0x59, 0x96, 0x41, 0x57, 0xe1, - 0x74, 0xc7, 0x73, 0xd9, 0xcb, 0xc6, 0x55, 0x62, 0x35, 0x5a, 0xb6, 0x43, 0xa4, 0x0b, 0x81, 0xc7, - 0x10, 0x3c, 0x70, 0xd0, 0x2b, 0x9d, 0xae, 0xa6, 0x93, 0xe0, 0x7e, 0x65, 0xa3, 0xaf, 0x71, 0xc6, - 0x06, 0x78, 0x8d, 0xf3, 0x69, 0xe5, 0xa8, 0x23, 0xbe, 0x78, 0x13, 0xf3, 0xc1, 0xac, 0x86, 0x32, - 0x65, 0x5b, 0x0f, 0xa7, 0x54, 0x59, 0x08, 0xc5, 0x4a, 0x7c, 0x7f, 0x6f, 0xd0, 0xf8, 0x11, 0xbd, - 0x41, 0x61, 0x50, 0xf5, 0xc4, 0x4f, 0x33, 0xa8, 0xba, 0xf8, 0xa6, 0x0a, 0xaa, 0x7e, 0xb5, 0x00, - 0x73, 0x71, 0x0b, 0xe4, 0xf8, 0x5f, 0x1a, 0xfd, 0x9a, 0x01, 0x73, 0x72, 0xf5, 0x70, 0x99, 0x44, - 0xfa, 0xf9, 0xd7, 0x33, 0x5a, 0xb4, 0xdc, 0x96, 0x52, 0x6f, 0x61, 0xb7, 0x62, 0xd2, 0x70, 0x42, - 0x3e, 0x7a, 0x01, 0xa6, 0x94, 0x3b, 0xfc, 0x48, 0xcf, 0x8e, 0x66, 0x99, 0x15, 0x15, 0xb2, 0xc0, - 0x3a, 0x3f, 0xf4, 0xaa, 0x01, 0x50, 0x97, 0x6a, 0x4e, 0xae, 0xae, 0x2b, 0x59, 0xad, 0x2e, 0xa5, - 0x40, 0x43, 0x63, 0x59, 0x81, 0x7c, 0xac, 0x09, 0x46, 0x9f, 0x67, 0x8e, 0x70, 0x65, 0xdd, 0xd1, - 0xf5, 0x94, 0x1f, 0x3d, 0x0e, 0xf6, 0x10, 0xc3, 0x34, 0x34, 0xa5, 0x34, 0x94, 0x8f, 0x23, 0x95, - 0x30, 0x9f, 0x06, 0x15, 0xb9, 0x48, 0xb7, 0x2d, 0x16, 0xbb, 0x58, 0xb5, 0x82, 0x5d, 0x31, 0x05, - 0xd5, 0xb6, 0x75, 0x41, 0x22, 0x70, 0x48, 0x63, 0x7e, 0x04, 0x66, 0x9e, 0xf5, 0xac, 0xce, 0xae, - 0xcd, 0x1c, 0xce, 0xf4, 0x9c, 0xf4, 0x76, 0x98, 0xb0, 0x1a, 0x8d, 0xb4, 0x97, 0xe4, 0x65, 0x0e, - 0xc6, 0x12, 0x3f, 0xd8, 0x91, 0xe8, 0xcf, 0x0d, 0x40, 0x91, 0xbb, 0xb2, 0x0d, 0x7a, 0xda, 0xa7, - 0xe7, 0xa3, 0x5d, 0x06, 0x4d, 0x3b, 0x1f, 0x5d, 0x54, 0x18, 0xac, 0x51, 0xa1, 0x97, 0x0d, 0x98, - 0xe2, 0x7f, 0xaf, 0xa9, 0xd3, 0xfe, 0xc8, 0x0f, 0x51, 0xb9, 0x46, 0x61, 0x95, 0x0a, 0x0d, 0xfa, - 0x8b, 0xa1, 0x14, 0xac, 0x8b, 0x34, 0xbf, 0x65, 0xc0, 0xc2, 0x9a, 0x1f, 0xd8, 0xee, 0x2a, 0xf1, - 0x03, 0xba, 0xf3, 0xd3, 0xfd, 0xa1, 0xdb, 0x1a, 0x24, 0x4e, 0x78, 0x15, 0xe6, 0xc4, 0x1d, 0x5f, - 0x77, 0xdb, 0x27, 0x81, 0x66, 0x6a, 0xab, 0xa5, 0xb6, 0x12, 0xc3, 0xe3, 0x44, 0x09, 0xca, 0x45, - 0x5c, 0xf6, 0x85, 0x5c, 0xf2, 0x51, 0x2e, 0xb5, 0x18, 0x1e, 0x27, 0x4a, 0x98, 0xdf, 0xcd, 0xc3, - 0x49, 0xd6, 0x8c, 0x58, 0x8c, 0xff, 0x67, 0xfb, 0xc5, 0xf8, 0x8f, 0xb8, 0xda, 0x98, 0xac, 0x23, - 0x44, 0xf8, 0xff, 0xaa, 0x01, 0xb3, 0x8d, 0x68, 0x4f, 0x67, 0xe3, 0x41, 0x49, 0x1b, 0x43, 0x1e, - 0x4f, 0x14, 0x03, 0xe2, 0xb8, 0x7c, 0xf4, 0x05, 0x03, 0x66, 0xa3, 0xd5, 0x94, 0x1b, 0xf0, 0x31, - 0x74, 0x92, 0x0a, 0x00, 0x8e, 0xc2, 0x7d, 0x1c, 0xaf, 0x82, 0xf9, 0x37, 0x86, 0x18, 0xd2, 0xe3, - 0x08, 0x60, 0x47, 0xb7, 0x60, 0x32, 0x68, 0xf9, 0x1c, 0x28, 0x5a, 0x3b, 0xe2, 0xa1, 0x6d, 0x6b, - 0xbd, 0xc6, 0xd8, 0x69, 0x76, 0x95, 0x80, 0x50, 0xfb, 0x50, 0xca, 0x32, 0xbf, 0x6a, 0xc0, 0xe4, - 0x25, 0x77, 0x5b, 0x6c, 0x4e, 0x1f, 0xce, 0xc0, 0x25, 0xa2, 0x2c, 0x27, 0x75, 0x9b, 0x16, 0x1a, - 0xe3, 0xcf, 0x44, 0x1c, 0x22, 0x0f, 0x6a, 0xbc, 0x97, 0x59, 0x3e, 0x19, 0xca, 0xea, 0x92, 0xbb, - 0xdd, 0xd7, 0xdf, 0xf6, 0xdb, 0x05, 0x38, 0xf1, 0x9c, 0xb5, 0x4f, 0x9c, 0xc0, 0x1a, 0x7e, 0x3b, - 0x7d, 0x12, 0xa6, 0xac, 0x0e, 0x8b, 0x67, 0xd5, 0xac, 0xe1, 0xd0, 0xc7, 0x10, 0xa2, 0xb0, 0x4e, - 0x17, 0xee, 0x2b, 0x3c, 0xbd, 0x45, 0xda, 0x8e, 0xb0, 0x12, 0xc3, 0xe3, 0x44, 0x09, 0x74, 0x09, - 0x90, 0x78, 0xac, 0x57, 0xae, 0xd7, 0xdd, 0xae, 0xc3, 0x77, 0x16, 0xee, 0x7e, 0x50, 0xc7, 0xb2, - 0x8d, 0x04, 0x05, 0x4e, 0x29, 0x85, 0x3e, 0x04, 0x8b, 0x75, 0xc6, 0x59, 0x18, 0xe9, 0x3a, 0x47, - 0x7e, 0x50, 0x53, 0xb1, 0xe4, 0x2b, 0x7d, 0xe8, 0x70, 0x5f, 0x0e, 0xb4, 0xa6, 0x7e, 0xe0, 0x7a, - 0x56, 0x93, 0xe8, 0x7c, 0xc7, 0xa3, 0x35, 0xad, 0x25, 0x28, 0x70, 0x4a, 0x29, 0xf4, 0x09, 0x98, - 0x0c, 0x76, 0x3d, 0xe2, 0xef, 0xba, 0xad, 0x86, 0xb8, 0x5e, 0x1f, 0xd1, 0x27, 0x25, 0x46, 0x7f, - 0x4b, 0x72, 0xd5, 0xa6, 0xb7, 0x04, 0xe1, 0x50, 0x26, 0xf2, 0x60, 0xdc, 0xaf, 0xbb, 0x1d, 0xe2, - 0x0b, 0xe3, 0xf6, 0x52, 0x26, 0xd2, 0x99, 0x8f, 0x45, 0xf3, 0x86, 0x31, 0x09, 0x58, 0x48, 0x32, - 0xbf, 0x9d, 0x83, 0x69, 0x9d, 0x70, 0x80, 0x2d, 0xe2, 0x93, 0x06, 0x4c, 0xd7, 0x5d, 0x27, 0xf0, - 0xdc, 0x16, 0xf7, 0xf4, 0x64, 0xa3, 0x7a, 0x29, 0xab, 0x55, 0x12, 0x58, 0x76, 0x4b, 0x73, 0x1a, - 0x69, 0x62, 0x70, 0x44, 0x28, 0xfa, 0x8c, 0x01, 0xb3, 0x61, 0x28, 0x54, 0xe8, 0x72, 0xca, 0xb4, - 0x22, 0x6a, 0xc7, 0x3d, 0x1f, 0x95, 0x84, 0xe3, 0xa2, 0xcd, 0x6d, 0x98, 0x8b, 0x8f, 0x36, 0xed, - 0xca, 0x8e, 0x25, 0xd6, 0x7a, 0x3e, 0xec, 0xca, 0xaa, 0xe5, 0xfb, 0x98, 0x61, 0xd0, 0x3b, 0xa0, - 0xd8, 0xb6, 0xbc, 0xa6, 0xed, 0x58, 0x2d, 0xd6, 0x8b, 0x79, 0x6d, 0x43, 0x12, 0x70, 0xac, 0x28, - 0xcc, 0x1f, 0x8d, 0xc1, 0x94, 0x76, 0x26, 0x39, 0xfe, 0xf3, 0x45, 0x24, 0x7f, 0x40, 0x3e, 0xc3, - 0xfc, 0x01, 0xcf, 0x03, 0xec, 0xd8, 0x8e, 0xed, 0xef, 0x1e, 0x31, 0x33, 0x01, 0xbb, 0x9a, 0xbc, - 0xa0, 0x38, 0x60, 0x8d, 0x5b, 0x78, 0xff, 0x53, 0x38, 0x24, 0x5f, 0xcb, 0xab, 0x86, 0xa6, 0x3c, - 0xc6, 0xb3, 0xb8, 0xef, 0xd6, 0x06, 0x66, 0x59, 0x2a, 0x93, 0xf3, 0x4e, 0xe0, 0xed, 0x1f, 0xaa, - 0x63, 0xb6, 0xa0, 0xe8, 0x11, 0xbf, 0xdb, 0xa6, 0x27, 0xa5, 0x89, 0xa1, 0xbb, 0x81, 0xc5, 0x0a, - 0x60, 0x51, 0x1e, 0x2b, 0x4e, 0x4b, 0x4f, 0xc3, 0x89, 0x48, 0x15, 0xd0, 0x1c, 0xe4, 0x6f, 0x92, - 0x7d, 0x3e, 0x4f, 0x30, 0xfd, 0x89, 0x16, 0x22, 0xb7, 0x64, 0xa2, 0x5b, 0xde, 0x9b, 0x7b, 0xca, - 0x30, 0x5d, 0x48, 0x3d, 0xf8, 0x1e, 0xe5, 0x12, 0x83, 0x8e, 0x45, 0x4b, 0x4b, 0x4d, 0xa0, 0xc6, - 0x82, 0x47, 0x84, 0x70, 0x9c, 0xf9, 0xe3, 0x71, 0x10, 0x57, 0xb8, 0x03, 0x6c, 0x3e, 0xfa, 0xcd, - 0x4d, 0xee, 0x08, 0x37, 0x37, 0x97, 0x60, 0xda, 0x76, 0xec, 0xc0, 0xb6, 0x5a, 0xcc, 0xa9, 0x21, - 0x94, 0xa3, 0x8c, 0x57, 0x9d, 0x5e, 0xd3, 0x70, 0x29, 0x7c, 0x22, 0x65, 0xd1, 0x15, 0x28, 0x30, - 0xed, 0x21, 0x26, 0xf0, 0xf0, 0xf7, 0xcc, 0x2c, 0xc4, 0x80, 0x3f, 0x62, 0xe1, 0x9c, 0x98, 0x45, - 0xcf, 0x73, 0x33, 0xa8, 0x63, 0xa7, 0x98, 0xc7, 0xa1, 0x45, 0x1f, 0xc3, 0xe3, 0x44, 0x09, 0xca, - 0x65, 0xc7, 0xb2, 0x5b, 0x5d, 0x8f, 0x84, 0x5c, 0xc6, 0xa3, 0x5c, 0x2e, 0xc4, 0xf0, 0x38, 0x51, - 0x02, 0xed, 0xc0, 0xb4, 0x80, 0xf1, 0x38, 0x9f, 0x89, 0x23, 0xb6, 0x92, 0xc5, 0x73, 0x5d, 0xd0, - 0x38, 0xe1, 0x08, 0x5f, 0xd4, 0x85, 0x79, 0xdb, 0xa9, 0xbb, 0x4e, 0xbd, 0xd5, 0xf5, 0xed, 0x3d, - 0x12, 0xbe, 0x20, 0x39, 0x8a, 0xb0, 0x53, 0x07, 0xbd, 0xd2, 0xfc, 0x5a, 0x9c, 0x1d, 0x4e, 0x4a, - 0x40, 0xaf, 0x18, 0x70, 0xaa, 0xee, 0x3a, 0x3e, 0x7b, 0xec, 0xbc, 0x47, 0xce, 0x7b, 0x9e, 0xeb, - 0x71, 0xd9, 0x93, 0x47, 0x94, 0xcd, 0x7c, 0x69, 0x2b, 0x69, 0x2c, 0x71, 0xba, 0x24, 0xf4, 0x22, - 0x14, 0x3b, 0x9e, 0xbb, 0x67, 0x37, 0x88, 0x27, 0x62, 0xc6, 0xd6, 0xb3, 0x48, 0xbe, 0x50, 0x15, - 0x3c, 0xc3, 0xad, 0x47, 0x42, 0xb0, 0x92, 0x67, 0x7e, 0xad, 0x08, 0x33, 0x51, 0x72, 0xf4, 0x71, - 0x80, 0x8e, 0xe7, 0xb6, 0x49, 0xb0, 0x4b, 0xd4, 0x4b, 0x80, 0xcd, 0x51, 0xdf, 0xf8, 0x4b, 0x7e, - 0x32, 0x6a, 0x83, 0x6e, 0x17, 0x21, 0x14, 0x6b, 0x12, 0x91, 0x07, 0x13, 0x37, 0xb9, 0x12, 0x15, - 0x36, 0xc5, 0x73, 0x99, 0x58, 0x40, 0x42, 0x32, 0x0b, 0x61, 0x17, 0x20, 0x2c, 0x05, 0xa1, 0x6d, - 0xc8, 0xdf, 0x22, 0xdb, 0xd9, 0xbc, 0x9b, 0xbd, 0x4e, 0xc4, 0xd9, 0xa4, 0x32, 0x71, 0xd0, 0x2b, - 0xe5, 0xaf, 0x93, 0x6d, 0x4c, 0x99, 0xd3, 0x76, 0x35, 0xf8, 0xfd, 0xb3, 0xd8, 0x2a, 0x46, 0x6c, - 0x57, 0xe4, 0x32, 0x9b, 0xb7, 0x4b, 0x80, 0xb0, 0x14, 0x84, 0x5e, 0x84, 0xc9, 0x5b, 0xd6, 0x1e, - 0xd9, 0xf1, 0x5c, 0x27, 0x10, 0xa1, 0x42, 0x23, 0x06, 0x87, 0x5f, 0x97, 0xec, 0x84, 0x5c, 0xa6, - 0xde, 0x15, 0x10, 0x87, 0xe2, 0xd0, 0x1e, 0x14, 0x1d, 0x72, 0x0b, 0x93, 0x96, 0x5d, 0x17, 0x41, - 0xb0, 0x23, 0x4e, 0xeb, 0x4d, 0xc1, 0x4d, 0x48, 0x66, 0x7a, 0x4f, 0xc2, 0xb0, 0x92, 0x45, 0xc7, - 0xf2, 0x86, 0xbb, 0x2d, 0x36, 0xaa, 0x11, 0xc7, 0x52, 0x9d, 0x33, 0xf9, 0x58, 0x5e, 0x72, 0xb7, - 0x31, 0x65, 0x4e, 0xd7, 0x48, 0x5d, 0xc5, 0xa9, 0x88, 0x6d, 0x6a, 0x33, 0xdb, 0xf8, 0x1c, 0xbe, - 0x46, 0x42, 0x28, 0xd6, 0x24, 0xd2, 0xbe, 0x6d, 0x0a, 0x27, 0x9d, 0xd8, 0xa8, 0x46, 0xec, 0xdb, - 0xa8, 0xcb, 0x8f, 0xf7, 0xad, 0x84, 0x61, 0x25, 0xcb, 0xfc, 0xea, 0x38, 0x4c, 0xeb, 0xc9, 0xa6, - 0x06, 0xd0, 0xd5, 0xca, 0x3e, 0xcd, 0x0d, 0x63, 0x9f, 0xd2, 0xe3, 0x85, 0xe6, 0x63, 0x97, 0x1e, - 0x86, 0xb5, 0xcc, 0xcc, 0xb3, 0xf0, 0x78, 0xa1, 0x01, 0x7d, 0x1c, 0x11, 0x3a, 0xc4, 0xb5, 0x3b, - 0x35, 0x72, 0xb8, 0x19, 0x50, 0x88, 0x1a, 0x39, 0x11, 0xc5, 0xfe, 0x18, 0x40, 0x98, 0x74, 0x49, - 0xdc, 0xbd, 0x28, 0xeb, 0x49, 0x4b, 0x06, 0xa5, 0x51, 0xa1, 0x47, 0x60, 0x9c, 0x2a, 0x4a, 0xd2, - 0x10, 0xcf, 0x34, 0xd5, 0x19, 0xee, 0x02, 0x83, 0x62, 0x81, 0x45, 0x4f, 0x51, 0x9b, 0x26, 0x54, - 0x6f, 0xe2, 0xf5, 0xe5, 0x42, 0x68, 0xd3, 0x84, 0x38, 0x1c, 0xa1, 0xa4, 0x55, 0x27, 0x54, 0x1b, - 0xb1, 0x99, 0xa4, 0x55, 0x9d, 0xa9, 0x28, 0xcc, 0x71, 0xcc, 0xa7, 0x10, 0xd3, 0x5e, 0x4c, 0x59, - 0x15, 0x34, 0x9f, 0x42, 0x0c, 0x8f, 0x13, 0x25, 0x68, 0x63, 0xc4, 0xb5, 0xd1, 0x14, 0x8f, 0x2e, - 0xec, 0x73, 0xe1, 0xf3, 0x9a, 0x6e, 0x99, 0x4f, 0xb3, 0xa1, 0x7f, 0x7f, 0x76, 0x89, 0xd3, 0x06, - 0x37, 0xcd, 0x47, 0x33, 0xa2, 0x3f, 0x02, 0x33, 0xd1, 0x3d, 0x8b, 0x4e, 0xa8, 0x8e, 0xe7, 0xee, - 0xd8, 0x2d, 0x12, 0xf7, 0xfd, 0x54, 0x39, 0x18, 0x4b, 0xfc, 0x60, 0xae, 0xf4, 0x3f, 0xcb, 0xc3, - 0xc9, 0xcd, 0xa6, 0xed, 0xdc, 0x8e, 0x79, 0x6d, 0xd3, 0x12, 0x9a, 0x1a, 0xc3, 0x26, 0x34, 0x0d, - 0x9f, 0x9c, 0x88, 0x8c, 0xb1, 0xe9, 0x4f, 0x4e, 0x64, 0x3a, 0xd9, 0x28, 0x2d, 0xfa, 0xa1, 0x01, - 0x0f, 0x5a, 0x0d, 0x6e, 0x45, 0x5a, 0x2d, 0x01, 0x0d, 0x85, 0xca, 0x15, 0xed, 0x8f, 0xa8, 0x13, - 0x92, 0x8d, 0x5f, 0x2e, 0x1f, 0x22, 0x95, 0x8f, 0xf8, 0xdb, 0x44, 0x0b, 0x1e, 0x3c, 0x8c, 0x14, - 0x1f, 0x5a, 0xfd, 0xa5, 0xcb, 0xf0, 0xd6, 0xbb, 0x0a, 0x1a, 0x6a, 0xb6, 0x7c, 0xd2, 0x80, 0x49, - 0xee, 0x94, 0xc4, 0x64, 0x87, 0x6e, 0x15, 0x56, 0xc7, 0xbe, 0x46, 0x3c, 0x5f, 0x66, 0x5a, 0xd2, - 0x0e, 0x5a, 0xe5, 0xea, 0x9a, 0xc0, 0x60, 0x8d, 0x8a, 0x6e, 0xc6, 0x37, 0x6d, 0xa7, 0x21, 0x86, - 0x49, 0x6d, 0xc6, 0xcf, 0xd9, 0x4e, 0x03, 0x33, 0x8c, 0xda, 0xae, 0xf3, 0x7d, 0xd3, 0x9e, 0x7c, - 0xc5, 0x80, 0x19, 0xf6, 0xc8, 0x2d, 0x3c, 0x02, 0x3c, 0xa9, 0x62, 0x2a, 0x78, 0x35, 0xce, 0x44, - 0x63, 0x2a, 0xee, 0xf4, 0x4a, 0x53, 0xfc, 0x59, 0x5c, 0x34, 0xc4, 0xe2, 0x83, 0xc2, 0x6f, 0xc0, - 0x22, 0x3f, 0x72, 0x43, 0x1f, 0x6b, 0x95, 0x97, 0xac, 0x26, 0x99, 0xe0, 0x90, 0x9f, 0xf9, 0x12, - 0x4c, 0xeb, 0x01, 0xf3, 0xe8, 0x49, 0x98, 0xea, 0xd8, 0x4e, 0x33, 0xfa, 0xb0, 0x4a, 0x79, 0x4a, - 0xab, 0x21, 0x0a, 0xeb, 0x74, 0xac, 0x98, 0x1b, 0x16, 0x8b, 0x39, 0x58, 0xab, 0xae, 0x5e, 0x2c, - 0xfc, 0x63, 0xfe, 0x7e, 0x1e, 0x4e, 0xa6, 0x3c, 0xcc, 0x40, 0xaf, 0x1a, 0x30, 0xce, 0xa2, 0xc4, - 0x65, 0xd4, 0xc4, 0x0b, 0x99, 0x3f, 0xfe, 0x58, 0x66, 0xc1, 0xe8, 0x62, 0x1e, 0xab, 0xed, 0x93, - 0x03, 0xb1, 0x10, 0x8e, 0x7e, 0xc3, 0x80, 0x29, 0x4b, 0x5b, 0x6a, 0x3c, 0x90, 0x64, 0x3b, 0xfb, - 0xca, 0x24, 0x56, 0x96, 0x16, 0x00, 0x17, 0x2e, 0x24, 0xbd, 0x2e, 0x4b, 0xef, 0x81, 0x29, 0xad, - 0x09, 0xc3, 0xac, 0x90, 0xa5, 0x67, 0x60, 0x6e, 0xa4, 0x15, 0xf6, 0x01, 0x18, 0x36, 0x71, 0x18, - 0x55, 0x58, 0xb7, 0xf4, 0x97, 0xa7, 0xaa, 0xc7, 0xc5, 0xd3, 0x53, 0x81, 0x35, 0xb7, 0x61, 0x2e, - 0x7e, 0xc8, 0xc9, 0xfc, 0xde, 0xf4, 0x5d, 0x30, 0x64, 0xaa, 0x2f, 0xf3, 0x2f, 0x72, 0x30, 0x21, - 0x5e, 0x77, 0xdd, 0x83, 0xd8, 0xd1, 0x9b, 0x91, 0xab, 0x92, 0xb5, 0x4c, 0x1e, 0xa5, 0xf5, 0x0d, - 0x1c, 0xf5, 0x63, 0x81, 0xa3, 0xcf, 0x65, 0x23, 0xee, 0xf0, 0xa8, 0xd1, 0xaf, 0x8c, 0xc1, 0x6c, - 0xec, 0xb5, 0x1c, 0x35, 0x55, 0x12, 0xc1, 0x52, 0x57, 0x33, 0x7d, 0x90, 0xa7, 0xe2, 0x9a, 0x0f, - 0x8f, 0x9b, 0xf2, 0x23, 0x19, 0x15, 0xaf, 0x64, 0x96, 0x8c, 0xf9, 0x67, 0xc9, 0x15, 0x87, 0x8d, - 0x03, 0xfa, 0x27, 0x03, 0xee, 0xef, 0xfb, 0xa8, 0x92, 0x25, 0xc4, 0xf0, 0xa2, 0x58, 0xb1, 0x20, - 0x33, 0x7e, 0xb7, 0xad, 0xee, 0x2d, 0xe2, 0x39, 0x0c, 0xe2, 0xe2, 0xd1, 0x13, 0x30, 0xcd, 0x54, - 0x2b, 0xdd, 0x53, 0x02, 0xd2, 0x11, 0x8e, 0x5a, 0xe6, 0xb2, 0xab, 0x69, 0x70, 0x1c, 0xa1, 0x32, - 0xbf, 0x6c, 0xc0, 0x62, 0xbf, 0xf4, 0x08, 0x03, 0x1c, 0x0c, 0x7f, 0x2e, 0x16, 0xdc, 0x5a, 0x4a, - 0x04, 0xb7, 0xc6, 0x8e, 0x86, 0x32, 0x8e, 0x55, 0x3b, 0x95, 0xe5, 0xef, 0x12, 0xbb, 0xf9, 0x59, - 0x03, 0x4e, 0xf7, 0x59, 0x4d, 0x89, 0x20, 0x67, 0xe3, 0xc8, 0x41, 0xce, 0xb9, 0x41, 0x83, 0x9c, - 0xcd, 0xbf, 0xca, 0xc3, 0x9c, 0xa8, 0x4f, 0x68, 0x5f, 0x3d, 0x15, 0x09, 0x11, 0x7e, 0x5b, 0x2c, - 0x44, 0x78, 0x21, 0x4e, 0xff, 0xb3, 0xf8, 0xe0, 0x37, 0x57, 0x7c, 0xf0, 0x4f, 0x72, 0x70, 0x2a, - 0x35, 0x6b, 0x03, 0xfa, 0x54, 0x8a, 0x6a, 0xb8, 0x9e, 0x71, 0x7a, 0x88, 0x01, 0x95, 0xc3, 0xa8, - 0x41, 0xb5, 0x5f, 0xd0, 0x83, 0x59, 0xf9, 0x56, 0xbf, 0x73, 0x0c, 0x89, 0x2e, 0x86, 0x8c, 0x6b, - 0x35, 0x7f, 0x25, 0x0f, 0x8f, 0x0e, 0xca, 0xe8, 0x4d, 0xfa, 0xee, 0xc1, 0x8f, 0xbc, 0x7b, 0xb8, - 0x47, 0x6a, 0xfb, 0x58, 0x9e, 0x40, 0x7c, 0x35, 0xaf, 0xd4, 0x5e, 0x72, 0x7e, 0x0e, 0x74, 0xab, - 0x37, 0x41, 0x4d, 0x3b, 0x99, 0xcb, 0x31, 0xdc, 0x0a, 0x27, 0x6a, 0x1c, 0x7c, 0xa7, 0x57, 0x9a, - 0x17, 0xf9, 0xdd, 0x6a, 0x24, 0x10, 0x40, 0x2c, 0x0b, 0xa1, 0x47, 0xa1, 0xe8, 0x71, 0xac, 0x8c, - 0xf4, 0x16, 0x57, 0xa3, 0x1c, 0x86, 0x15, 0x16, 0x7d, 0x42, 0xb3, 0x85, 0xc7, 0x8e, 0xeb, 0x95, - 0xfe, 0x61, 0x37, 0xbe, 0x2f, 0x40, 0xd1, 0x97, 0x59, 0x19, 0xb9, 0x5b, 0xfe, 0xf1, 0x01, 0x1f, - 0x10, 0xd0, 0xa3, 0x93, 0x4c, 0xd1, 0xc8, 0xdb, 0xa7, 0x12, 0x38, 0x2a, 0x96, 0xc8, 0x54, 0xa7, - 0x16, 0xee, 0x63, 0x84, 0x94, 0x13, 0xcb, 0xf7, 0x0c, 0x98, 0x12, 0xa3, 0x75, 0x0f, 0xde, 0x34, - 0xdc, 0x88, 0xbe, 0x69, 0x38, 0x9f, 0xc9, 0xde, 0xd1, 0xe7, 0x41, 0xc3, 0x0d, 0x98, 0xd6, 0x13, - 0xf7, 0xa0, 0xe7, 0xb5, 0xbd, 0xcf, 0x18, 0x25, 0x1b, 0x87, 0xdc, 0x1d, 0xc3, 0x7d, 0xd1, 0xfc, - 0x52, 0x51, 0xf5, 0x22, 0xf3, 0x43, 0xe8, 0x73, 0xd0, 0x38, 0x74, 0x0e, 0xea, 0x53, 0x20, 0x97, - 0xfd, 0x14, 0xb8, 0x02, 0x45, 0xb9, 0x41, 0x09, 0x35, 0xfe, 0xb0, 0x1e, 0xbb, 0x46, 0x6d, 0x01, - 0xca, 0x4c, 0x9b, 0xb8, 0xec, 0xa8, 0xa5, 0xc6, 0x50, 0x6d, 0x9c, 0x8a, 0x0d, 0x7a, 0x11, 0xa6, - 0x6e, 0xb9, 0xde, 0xcd, 0x96, 0x6b, 0xb1, 0x7c, 0xab, 0x90, 0xc5, 0x05, 0x8b, 0x72, 0x78, 0xf1, - 0x80, 0xef, 0xeb, 0x21, 0x7f, 0xac, 0x0b, 0x43, 0x65, 0x98, 0x6d, 0xdb, 0x0e, 0x26, 0x56, 0x43, - 0x3d, 0x5d, 0x18, 0xe3, 0x09, 0x21, 0xa5, 0x91, 0xbb, 0x11, 0x45, 0xe3, 0x38, 0x3d, 0xfa, 0x18, - 0x14, 0x7d, 0x91, 0x89, 0x27, 0x9b, 0xab, 0x30, 0x75, 0x66, 0xe4, 0x4c, 0xc3, 0xbe, 0x93, 0x10, - 0xac, 0x04, 0xa2, 0x75, 0x58, 0xf0, 0x44, 0xae, 0x8b, 0xc8, 0xd7, 0x1a, 0xf8, 0xfa, 0x64, 0x79, - 0x07, 0x71, 0x0a, 0x1e, 0xa7, 0x96, 0xa2, 0x56, 0x0c, 0xcb, 0x40, 0xc5, 0xef, 0x04, 0x34, 0x37, - 0x3a, 0x9b, 0xf0, 0x0d, 0x2c, 0xb0, 0x87, 0x3d, 0x85, 0x29, 0x8e, 0xf0, 0x14, 0xa6, 0x06, 0xa7, - 0xe2, 0x28, 0x96, 0x91, 0x83, 0x25, 0x01, 0xd1, 0xb4, 0x47, 0x35, 0x8d, 0x08, 0xa7, 0x97, 0x45, - 0xd7, 0x61, 0xd2, 0x23, 0xec, 0x7c, 0x51, 0x96, 0x97, 0xef, 0x43, 0x87, 0x19, 0x61, 0xc9, 0x00, - 0x87, 0xbc, 0xe8, 0xb8, 0x5b, 0xd1, 0x9c, 0x88, 0x57, 0x32, 0xfc, 0xde, 0x94, 0x18, 0xfb, 0x3e, - 0x99, 0x72, 0xcc, 0x37, 0x66, 0xe0, 0x44, 0xc4, 0xb7, 0x80, 0x1e, 0x86, 0x02, 0x4b, 0x51, 0xc2, - 0xb6, 0x87, 0x62, 0xb8, 0x85, 0xf1, 0xce, 0xe1, 0x38, 0xf4, 0x39, 0x03, 0x66, 0x3b, 0x11, 0x2f, - 0xac, 0xdc, 0x39, 0x47, 0xbc, 0xe7, 0x8b, 0xba, 0x76, 0xb5, 0x6c, 0xc2, 0x51, 0x61, 0x38, 0x2e, - 0x9d, 0x2e, 0x40, 0x11, 0x79, 0xd7, 0x22, 0x1e, 0xa3, 0x16, 0x36, 0x8e, 0x62, 0xb1, 0x12, 0x45, - 0xe3, 0x38, 0x3d, 0x1d, 0x61, 0xd6, 0xba, 0x51, 0x3e, 0x44, 0x53, 0x96, 0x0c, 0x70, 0xc8, 0x0b, - 0x3d, 0x03, 0x33, 0x22, 0x15, 0x5e, 0xd5, 0x6d, 0x5c, 0xb4, 0xfc, 0x5d, 0x61, 0xdc, 0xab, 0xc3, - 0xc8, 0x4a, 0x04, 0x8b, 0x63, 0xd4, 0xac, 0x6d, 0x61, 0xbe, 0x41, 0xc6, 0x60, 0x3c, 0x9a, 0x6c, - 0x79, 0x25, 0x8a, 0xc6, 0x71, 0x7a, 0xf4, 0x0e, 0x6d, 0xdf, 0xe7, 0xf7, 0x74, 0x6a, 0x37, 0x48, - 0xd9, 0xfb, 0xcb, 0x30, 0xdb, 0x65, 0x67, 0xa1, 0x86, 0x44, 0x8a, 0xf5, 0xa8, 0x04, 0x5e, 0x8d, - 0xa2, 0x71, 0x9c, 0x1e, 0x3d, 0x0d, 0x27, 0x3c, 0xba, 0xbb, 0x29, 0x06, 0xfc, 0xf2, 0x4e, 0xdd, - 0xcd, 0x60, 0x1d, 0x89, 0xa3, 0xb4, 0xe8, 0x59, 0x98, 0x0f, 0x93, 0x57, 0x49, 0x06, 0xfc, 0x36, - 0x4f, 0xe5, 0x65, 0x29, 0xc7, 0x09, 0x70, 0xb2, 0x0c, 0xfa, 0x05, 0x98, 0xd3, 0x7a, 0x62, 0xcd, - 0x69, 0x90, 0xdb, 0x22, 0xc1, 0x10, 0x4b, 0x93, 0xb6, 0x12, 0xc3, 0xe1, 0x04, 0x35, 0x7a, 0x2f, - 0xcc, 0xd4, 0xdd, 0x56, 0x8b, 0xed, 0x71, 0x3c, 0xd1, 0x2f, 0xcf, 0x24, 0xc4, 0x73, 0x2e, 0x45, - 0x30, 0x38, 0x46, 0x89, 0x2e, 0x01, 0x72, 0xb7, 0x7d, 0xe2, 0xed, 0x91, 0xc6, 0xb3, 0xfc, 0xd3, - 0x96, 0x54, 0xc5, 0x9f, 0x88, 0xc6, 0xfd, 0x5e, 0x4e, 0x50, 0xe0, 0x94, 0x52, 0x2c, 0xad, 0x8b, - 0xf6, 0xa2, 0x68, 0x26, 0x8b, 0x8f, 0xb2, 0xc4, 0x4f, 0xee, 0x77, 0x7d, 0x4e, 0xe4, 0xc1, 0x38, - 0x0f, 0xc3, 0xce, 0x26, 0xa5, 0x90, 0x9e, 0xf3, 0x33, 0xd4, 0x11, 0x1c, 0x8a, 0x85, 0x24, 0xf4, - 0x71, 0x98, 0xdc, 0x96, 0x09, 0xa0, 0x17, 0xe7, 0xb2, 0xd0, 0x8b, 0xb1, 0x5c, 0xe6, 0xe1, 0xc9, - 0x54, 0x21, 0x70, 0x28, 0x12, 0x3d, 0x02, 0x53, 0x17, 0xab, 0x65, 0x35, 0x0b, 0xe7, 0xd9, 0xe8, - 0x8f, 0xd1, 0x22, 0x58, 0x47, 0xd0, 0x15, 0xa6, 0xec, 0x25, 0xc4, 0x86, 0x38, 0xd4, 0xb7, 0x49, - 0xf3, 0x87, 0x52, 0xb3, 0xeb, 0x48, 0x5c, 0x5b, 0x3c, 0x19, 0xa3, 0x16, 0x70, 0xac, 0x28, 0xd0, - 0x0b, 0x30, 0x25, 0xf4, 0x05, 0xdb, 0x9b, 0x16, 0x8e, 0xf6, 0x5a, 0x0d, 0x87, 0x2c, 0xb0, 0xce, - 0x8f, 0xdd, 0x32, 0xb1, 0xbc, 0xb8, 0xe4, 0x42, 0xb7, 0xd5, 0x5a, 0x3c, 0xc5, 0xf6, 0xcd, 0xf0, - 0x96, 0x29, 0x44, 0x61, 0x9d, 0x0e, 0x3d, 0x2e, 0x23, 0x27, 0xde, 0x12, 0xb9, 0x76, 0x53, 0x91, - 0x13, 0xca, 0xca, 0xed, 0x13, 0xd8, 0x7b, 0xfa, 0x2e, 0x21, 0x0b, 0xdb, 0xb0, 0x24, 0x4d, 0xac, - 0xe4, 0x22, 0x59, 0x5c, 0x8c, 0x78, 0x09, 0x96, 0xae, 0xf7, 0xa5, 0xc4, 0x87, 0x70, 0x41, 0xdb, - 0x90, 0xb7, 0x5a, 0xdb, 0x8b, 0xf7, 0x67, 0x61, 0x2b, 0xaa, 0x4f, 0xd5, 0xf2, 0x60, 0x9c, 0xf2, - 0x7a, 0x05, 0x53, 0xe6, 0xe6, 0x2b, 0x39, 0xe5, 0x95, 0x57, 0xa9, 0x16, 0x5f, 0xd2, 0x67, 0xb5, - 0x91, 0xc5, 0xa7, 0x18, 0x13, 0x59, 0xd2, 0xb9, 0x42, 0x4a, 0x9d, 0xd3, 0x1d, 0xb5, 0x8e, 0x33, - 0xc9, 0xa3, 0x11, 0x4d, 0x23, 0xc9, 0x4f, 0x73, 0xd1, 0x55, 0x6c, 0x7e, 0x7f, 0x5c, 0x39, 0xa1, - 0x62, 0xa1, 0x00, 0x1e, 0x14, 0x6c, 0x3f, 0xb0, 0xdd, 0x0c, 0x9f, 0x6d, 0xc5, 0xf2, 0x2f, 0xb2, - 0x00, 0x56, 0x86, 0xc0, 0x5c, 0x14, 0x95, 0xe9, 0x34, 0x6d, 0xe7, 0xb6, 0x68, 0xfe, 0x95, 0xcc, - 0xef, 0xf8, 0xb9, 0x4c, 0x86, 0xc0, 0x5c, 0x14, 0xba, 0xc1, 0x67, 0x5a, 0x36, 0x9f, 0xdd, 0x8c, - 0x7f, 0x4d, 0x37, 0x3a, 0xe3, 0xa8, 0x2c, 0xbf, 0x6d, 0x0b, 0x1b, 0x66, 0x44, 0x59, 0xb5, 0x8d, - 0xb5, 0x34, 0x59, 0xb5, 0x8d, 0x35, 0x4c, 0x85, 0xa0, 0xd7, 0x0c, 0x00, 0x4b, 0x7d, 0x56, 0x36, - 0x9b, 0x4f, 0x0a, 0xf4, 0xfb, 0x4c, 0x2d, 0x8f, 0x39, 0x0b, 0xb1, 0x58, 0x93, 0x8c, 0x5e, 0x84, - 0x09, 0x8b, 0x7f, 0x10, 0x45, 0x84, 0xf3, 0x65, 0xf3, 0x95, 0x9f, 0x58, 0x0d, 0x58, 0x1c, 0xa3, - 0x40, 0x61, 0x29, 0x90, 0xca, 0x0e, 0x3c, 0x8b, 0xec, 0xd8, 0x37, 0x45, 0x5c, 0x5f, 0x6d, 0xe4, - 0xbc, 0xc6, 0x94, 0x59, 0x9a, 0x6c, 0x81, 0xc2, 0x52, 0xa0, 0xf9, 0xaf, 0x06, 0x68, 0xdf, 0x20, - 0x0c, 0x03, 0xbd, 0x8c, 0x81, 0x03, 0xbd, 0x72, 0x43, 0x06, 0x7a, 0xe5, 0x87, 0x0a, 0xf4, 0x1a, - 0x1b, 0x3e, 0xd0, 0xab, 0xd0, 0x3f, 0xd0, 0xcb, 0x7c, 0xdd, 0x80, 0xf9, 0xc4, 0x9c, 0x8c, 0x7f, - 0xeb, 0xd9, 0x18, 0xf0, 0x5b, 0xcf, 0xab, 0x30, 0x27, 0x12, 0xb1, 0xd6, 0x3a, 0x2d, 0x3b, 0xf5, - 0x85, 0xeb, 0x56, 0x0c, 0x8f, 0x13, 0x25, 0xcc, 0x3f, 0x36, 0x60, 0x4a, 0x7b, 0x90, 0x43, 0xdb, - 0xc1, 0x1e, 0x2e, 0x89, 0x6a, 0x84, 0x39, 0x68, 0x99, 0x7b, 0x95, 0xe3, 0xb8, 0xa7, 0xbf, 0xa9, - 0x25, 0xfd, 0x0b, 0x3d, 0xfd, 0x14, 0x8a, 0x05, 0x96, 0xa7, 0x73, 0x23, 0xfc, 0x3b, 0xde, 0x79, - 0x3d, 0x9d, 0x1b, 0xe9, 0x60, 0x86, 0x61, 0xe2, 0xa8, 0x2e, 0x17, 0x31, 0x80, 0x5a, 0xca, 0x5b, - 0x8b, 0x9e, 0xd8, 0x18, 0x0e, 0x9d, 0x81, 0x3c, 0x71, 0x1a, 0xe2, 0xe0, 0xa1, 0xbe, 0xf1, 0x72, - 0xde, 0x69, 0x60, 0x0a, 0x37, 0x2f, 0xc3, 0x74, 0x8d, 0xd4, 0x3d, 0x12, 0x3c, 0x47, 0xf6, 0x07, - 0xfe, 0x68, 0xcc, 0x4d, 0xb2, 0x1f, 0xff, 0x68, 0x0c, 0x2d, 0x4e, 0xe1, 0xe6, 0xef, 0x19, 0x10, - 0x4b, 0x8a, 0xac, 0x79, 0xfd, 0x8c, 0x7e, 0x5e, 0xbf, 0x88, 0x7f, 0x2a, 0x77, 0xa8, 0x7f, 0xea, - 0x12, 0xa0, 0xb6, 0x15, 0xd4, 0x77, 0x23, 0x29, 0xc0, 0xc5, 0x99, 0x2f, 0x7c, 0xfe, 0x97, 0xa0, - 0xc0, 0x29, 0xa5, 0xcc, 0x4f, 0x1b, 0x90, 0x48, 0x99, 0x8c, 0xba, 0x50, 0x60, 0xa4, 0xe2, 0x62, - 0xa4, 0x3a, 0xda, 0x8a, 0x4e, 0x3e, 0x28, 0x0f, 0x07, 0x8a, 0xfd, 0xc5, 0x5c, 0x9a, 0xf9, 0x32, - 0xad, 0x4b, 0xfc, 0x03, 0xe0, 0x6f, 0x87, 0x09, 0x22, 0xbe, 0x0d, 0xc2, 0x8f, 0xe5, 0xca, 0x6a, - 0x92, 0x9f, 0x04, 0x91, 0x78, 0x7a, 0x76, 0x93, 0xde, 0x3f, 0xe9, 0x4b, 0xe1, 0x8f, 0xb6, 0xd4, - 0xd9, 0x6d, 0x35, 0x8a, 0xc6, 0x71, 0x7a, 0xf3, 0x13, 0x30, 0xa5, 0xbd, 0x31, 0x67, 0xcb, 0xf2, - 0xb6, 0x55, 0x0f, 0xe2, 0xd3, 0xf9, 0x3c, 0x05, 0x62, 0x8e, 0x63, 0x2e, 0x1f, 0x1e, 0xc7, 0x17, - 0x9b, 0xce, 0x22, 0x7a, 0x4f, 0x60, 0x29, 0x33, 0x8f, 0x34, 0xc9, 0x6d, 0x99, 0x6a, 0x4f, 0x32, - 0xc3, 0x14, 0x88, 0x39, 0xce, 0xbc, 0x06, 0x45, 0xf9, 0xb4, 0x96, 0xbd, 0x4f, 0x93, 0xee, 0x08, - 0xfd, 0x7d, 0x9a, 0xeb, 0x05, 0x98, 0x61, 0xe8, 0x9c, 0xf1, 0x1d, 0xfb, 0xa2, 0xeb, 0x07, 0xf2, - 0x3d, 0x30, 0x77, 0x3a, 0x6e, 0xae, 0x31, 0x18, 0x56, 0x58, 0x73, 0x1e, 0x66, 0x95, 0x37, 0x51, - 0x84, 0x4a, 0x7d, 0x3b, 0x0f, 0xd3, 0x91, 0x2f, 0x4d, 0xde, 0x7d, 0xe6, 0x0f, 0x3e, 0x47, 0x53, - 0xbc, 0x82, 0xf9, 0x21, 0xbd, 0x82, 0xba, 0x1b, 0x76, 0xec, 0x78, 0xdd, 0xb0, 0x85, 0x6c, 0xdc, - 0xb0, 0x01, 0x4c, 0x88, 0xaf, 0xf0, 0x0b, 0x3d, 0xbc, 0x91, 0x51, 0x96, 0x0f, 0xf1, 0xc0, 0x9c, - 0x69, 0x41, 0xb9, 0x9b, 0x4b, 0x51, 0xe6, 0x37, 0x0a, 0x30, 0x13, 0xcd, 0xfb, 0x31, 0xc0, 0x48, - 0xbe, 0x23, 0x31, 0x92, 0x43, 0x7a, 0x45, 0xf2, 0xa3, 0x7a, 0x45, 0xc6, 0x46, 0xf5, 0x8a, 0x14, - 0x8e, 0xe0, 0x15, 0x49, 0xfa, 0x34, 0xc6, 0x07, 0xf6, 0x69, 0xbc, 0x4f, 0x5d, 0xe9, 0x4f, 0x44, - 0xee, 0xc0, 0xc2, 0x2b, 0x7d, 0x14, 0x1d, 0x86, 0x15, 0xb7, 0x91, 0x1a, 0x1a, 0x51, 0xbc, 0xcb, - 0xe9, 0xcf, 0x4b, 0xbd, 0x81, 0x1f, 0xde, 0xf1, 0xfa, 0x96, 0x21, 0x6e, 0xdf, 0x9f, 0x84, 0x29, - 0x31, 0x9f, 0x98, 0x25, 0x00, 0x51, 0x2b, 0xa2, 0x16, 0xa2, 0xb0, 0x4e, 0xc7, 0x3e, 0x86, 0x16, - 0xfd, 0xfa, 0x1b, 0x73, 0x32, 0xe9, 0x1f, 0x43, 0x8b, 0x7d, 0x2d, 0x2e, 0x4e, 0x6f, 0x7e, 0x0c, - 0x4e, 0xa5, 0xda, 0x7c, 0xec, 0x10, 0xcc, 0x94, 0x14, 0x69, 0x08, 0x02, 0xad, 0x1a, 0xb1, 0xb4, - 0x90, 0x4b, 0xd7, 0xfb, 0x52, 0xe2, 0x43, 0xb8, 0x98, 0x5f, 0xcf, 0xc3, 0x4c, 0xf4, 0x4b, 0x1a, - 0xe8, 0x96, 0x3a, 0x21, 0x66, 0x72, 0x38, 0xe5, 0x6c, 0xb5, 0x44, 0x15, 0x7d, 0xdd, 0x3d, 0xb7, - 0xd8, 0xfc, 0xda, 0x56, 0x59, 0x33, 0x8e, 0x4f, 0xb0, 0xf0, 0xb3, 0x08, 0x71, 0xec, 0x63, 0x19, - 0x61, 0x40, 0xb5, 0x88, 0x21, 0xc8, 0x5c, 0x7a, 0x18, 0x22, 0xad, 0x44, 0x61, 0x4d, 0x2c, 0xd5, - 0x2d, 0x7b, 0xc4, 0xb3, 0x77, 0x6c, 0xf5, 0x15, 0x30, 0xb6, 0x73, 0x5f, 0x13, 0x30, 0xac, 0xb0, - 0xe6, 0xcb, 0x39, 0x08, 0xbf, 0x79, 0xc8, 0x32, 0xde, 0xfb, 0x9a, 0x01, 0x27, 0x86, 0xed, 0xd2, - 0xa8, 0x1f, 0x97, 0x08, 0x39, 0x8a, 0x70, 0x2b, 0x0d, 0x82, 0x23, 0x12, 0x7f, 0x0a, 0xdf, 0x3a, - 0xb4, 0x60, 0x36, 0xf6, 0xdc, 0x2b, 0xf3, 0x98, 0xd6, 0x2f, 0xe5, 0x61, 0x52, 0x3d, 0x98, 0x43, - 0xef, 0x61, 0x69, 0xa3, 0x77, 0x5d, 0x99, 0xcc, 0xfb, 0xad, 0x5a, 0x72, 0xe7, 0x5d, 0xb7, 0x71, - 0xa7, 0x57, 0x9a, 0x55, 0xc4, 0x1c, 0x84, 0x45, 0x01, 0x6a, 0x2e, 0x77, 0xbd, 0x56, 0xdc, 0x5c, - 0xbe, 0x8a, 0xd7, 0x31, 0x85, 0xa3, 0xdb, 0x30, 0xc1, 0x93, 0xf6, 0xc8, 0xe8, 0x95, 0x8d, 0x8c, - 0x1e, 0xf9, 0x71, 0xbb, 0x33, 0xec, 0x06, 0xfe, 0xdf, 0xc7, 0x52, 0x1c, 0xd5, 0x92, 0xdb, 0x6e, - 0x63, 0x3f, 0x9e, 0x0c, 0xba, 0xe2, 0x36, 0xf6, 0x31, 0xc3, 0xa0, 0x67, 0x60, 0x26, 0xb0, 0xdb, - 0xc4, 0xed, 0x06, 0xfa, 0x17, 0xe5, 0xf2, 0xe1, 0xf5, 0xc5, 0x56, 0x04, 0x8b, 0x63, 0xd4, 0x54, - 0xcb, 0xde, 0xf0, 0x5d, 0x87, 0x65, 0x78, 0x1a, 0x8f, 0xfa, 0x3a, 0x2f, 0xd5, 0x2e, 0x6f, 0xb2, - 0x04, 0x4f, 0x8a, 0x82, 0x52, 0xdb, 0xec, 0x55, 0x8e, 0x47, 0xc4, 0xed, 0xe1, 0x5c, 0xf8, 0x76, - 0x9a, 0xc3, 0xb1, 0xa2, 0x30, 0xaf, 0xc2, 0x6c, 0xac, 0xa9, 0xf2, 0x60, 0x62, 0xa4, 0x1f, 0x4c, - 0x06, 0xcb, 0xbc, 0xfc, 0x07, 0x06, 0xcc, 0x27, 0x16, 0xef, 0xa0, 0xc1, 0xd6, 0x71, 0x35, 0x92, - 0x3b, 0xba, 0x1a, 0xc9, 0x0f, 0xa7, 0x46, 0x2a, 0xcb, 0xdf, 0x79, 0xe3, 0xec, 0x7d, 0xdf, 0x7d, - 0xe3, 0xec, 0x7d, 0xdf, 0x7f, 0xe3, 0xec, 0x7d, 0x2f, 0x1f, 0x9c, 0x35, 0xbe, 0x73, 0x70, 0xd6, - 0xf8, 0xee, 0xc1, 0x59, 0xe3, 0xfb, 0x07, 0x67, 0x8d, 0x7f, 0x3c, 0x38, 0x6b, 0xbc, 0xfe, 0xa3, - 0xb3, 0xf7, 0x3d, 0x5f, 0x94, 0xd3, 0xe4, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x88, 0xc5, - 0xd7, 0x3a, 0x8d, 0x00, 0x00, + // 7541 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x23, 0xd7, + 0x75, 0xb0, 0x87, 0x14, 0x25, 0xf2, 0xe8, 0xff, 0xae, 0x36, 0x2b, 0xcb, 0xde, 0xa5, 0x33, 0x0e, + 0xfc, 0x39, 0x5f, 0x13, 0x29, 0xf1, 0x4f, 0xeb, 0xc4, 0x86, 0x5b, 0x52, 0xda, 0xf5, 0x6a, 0x2d, + 0x69, 0xb5, 0x97, 0xda, 0xdd, 0xc4, 0x89, 0x93, 0x8c, 0xc8, 0x2b, 0x6a, 0x56, 0xe4, 0x0c, 0x33, + 0x33, 0xd4, 0xae, 0x1c, 0x23, 0xb1, 0x1b, 0xd8, 0x4d, 0x8b, 0x04, 0x71, 0x9b, 0x04, 0x45, 0x51, + 0xa4, 0x08, 0x0a, 0x03, 0xfd, 0x49, 0x9f, 0x82, 0x16, 0x7d, 0x09, 0xd0, 0xa2, 0xf9, 0x69, 0xfa, + 0xd0, 0x22, 0x29, 0xda, 0x26, 0x29, 0x10, 0xb6, 0x56, 0xfa, 0xd2, 0xa2, 0x45, 0x50, 0x20, 0x45, + 0x91, 0x7d, 0x2a, 0xee, 0xef, 0xdc, 0x19, 0x0e, 0xb5, 0xa4, 0x38, 0xda, 0x18, 0x6d, 0xde, 0xc8, + 0x7b, 0xce, 0x3d, 0xe7, 0xdc, 0xdf, 0x73, 0xee, 0xb9, 0xe7, 0x9e, 0x81, 0xb5, 0xba, 0x1d, 0xec, + 0xb6, 0xb7, 0x17, 0xab, 0x6e, 0x73, 0xc9, 0xf2, 0xea, 0x6e, 0xcb, 0x73, 0x6f, 0xb0, 0x1f, 0xef, + 0xf4, 0xdc, 0x46, 0xc3, 0x6d, 0x07, 0xfe, 0x52, 0x6b, 0xaf, 0xbe, 0x64, 0xb5, 0x6c, 0x7f, 0x49, + 0x95, 0xec, 0xbf, 0xdb, 0x6a, 0xb4, 0x76, 0xad, 0x77, 0x2f, 0xd5, 0x89, 0x43, 0x3c, 0x2b, 0x20, + 0xb5, 0xc5, 0x96, 0xe7, 0x06, 0x2e, 0x7a, 0x2a, 0xa4, 0xb6, 0x28, 0xa9, 0xb1, 0x1f, 0x1f, 0x96, + 0x75, 0x17, 0x5b, 0x7b, 0xf5, 0x45, 0x4a, 0x6d, 0x51, 0x95, 0x48, 0x6a, 0x0b, 0xef, 0xd4, 0x64, + 0xa9, 0xbb, 0x75, 0x77, 0x89, 0x11, 0xdd, 0x6e, 0xef, 0xb0, 0x7f, 0xec, 0x0f, 0xfb, 0xc5, 0x99, + 0x2d, 0x3c, 0xb8, 0xf7, 0x84, 0xbf, 0x68, 0xbb, 0x54, 0xb6, 0xa5, 0x6d, 0x2b, 0xa8, 0xee, 0x2e, + 0xed, 0x77, 0x49, 0xb4, 0x60, 0x6a, 0x48, 0x55, 0xd7, 0x23, 0x49, 0x38, 0x8f, 0x85, 0x38, 0x4d, + 0xab, 0xba, 0x6b, 0x3b, 0xc4, 0x3b, 0x08, 0x5b, 0xdd, 0x24, 0x81, 0x95, 0x54, 0x6b, 0xa9, 0x57, + 0x2d, 0xaf, 0xed, 0x04, 0x76, 0x93, 0x74, 0x55, 0xf8, 0xf9, 0x3b, 0x55, 0xf0, 0xab, 0xbb, 0xa4, + 0x69, 0x75, 0xd5, 0x7b, 0xb4, 0x57, 0xbd, 0x76, 0x60, 0x37, 0x96, 0x6c, 0x27, 0xf0, 0x03, 0x2f, + 0x5e, 0xc9, 0xfc, 0x7a, 0x16, 0x0a, 0xa5, 0xb5, 0x72, 0x25, 0xb0, 0x82, 0xb6, 0x8f, 0x5e, 0x35, + 0x60, 0xa2, 0xe1, 0x5a, 0xb5, 0xb2, 0xd5, 0xb0, 0x9c, 0x2a, 0xf1, 0xe6, 0x8d, 0x07, 0x8c, 0x87, + 0xc7, 0x1f, 0x59, 0x5b, 0x1c, 0x66, 0xbc, 0x16, 0x4b, 0x37, 0x7d, 0x4c, 0x7c, 0xb7, 0xed, 0x55, + 0x09, 0x26, 0x3b, 0xe5, 0xb9, 0x6f, 0x75, 0x8a, 0xf7, 0x1c, 0x76, 0x8a, 0x13, 0x6b, 0x1a, 0x27, + 0x1c, 0xe1, 0x8b, 0xbe, 0x60, 0xc0, 0x6c, 0xd5, 0x72, 0x2c, 0xef, 0x60, 0xcb, 0xf2, 0xea, 0x24, + 0x78, 0xc6, 0x73, 0xdb, 0xad, 0xf9, 0xcc, 0x09, 0x48, 0x73, 0xaf, 0x90, 0x66, 0x76, 0x39, 0xce, + 0x0e, 0x77, 0x4b, 0xc0, 0xe4, 0xf2, 0x03, 0x6b, 0xbb, 0x41, 0x74, 0xb9, 0xb2, 0x27, 0x29, 0x57, + 0x25, 0xce, 0x0e, 0x77, 0x4b, 0x60, 0xbe, 0x92, 0x85, 0xd9, 0xd2, 0x5a, 0x79, 0xcb, 0xb3, 0x76, + 0x76, 0xec, 0x2a, 0x76, 0xdb, 0x81, 0xed, 0xd4, 0xd1, 0xdb, 0x61, 0xcc, 0x76, 0xea, 0x1e, 0xf1, + 0x7d, 0x36, 0x90, 0x85, 0xf2, 0xb4, 0x20, 0x3a, 0xb6, 0xca, 0x8b, 0xb1, 0x84, 0xa3, 0xc7, 0x61, + 0xdc, 0x27, 0xde, 0xbe, 0x5d, 0x25, 0x9b, 0xae, 0x17, 0xb0, 0x9e, 0xce, 0x95, 0x4f, 0x09, 0xf4, + 0xf1, 0x4a, 0x08, 0xc2, 0x3a, 0x1e, 0xad, 0xe6, 0xb9, 0x6e, 0x20, 0xe0, 0xac, 0x23, 0x0a, 0x61, + 0x35, 0x1c, 0x82, 0xb0, 0x8e, 0x87, 0x5e, 0x33, 0x60, 0xc6, 0x0f, 0xec, 0xea, 0x9e, 0xed, 0x10, + 0xdf, 0x5f, 0x76, 0x9d, 0x1d, 0xbb, 0x3e, 0x9f, 0x63, 0xbd, 0xb8, 0x31, 0x5c, 0x2f, 0x56, 0x62, + 0x54, 0xcb, 0x73, 0x87, 0x9d, 0xe2, 0x4c, 0xbc, 0x14, 0x77, 0x71, 0x47, 0x2b, 0x30, 0x63, 0x39, + 0x8e, 0x1b, 0x58, 0x81, 0xed, 0x3a, 0x9b, 0x1e, 0xd9, 0xb1, 0x6f, 0xcd, 0x8f, 0xb0, 0xe6, 0xcc, + 0x8b, 0xe6, 0xcc, 0x94, 0x62, 0x70, 0xdc, 0x55, 0xc3, 0x5c, 0x81, 0xf9, 0x52, 0x73, 0xdb, 0xf2, + 0x7d, 0xab, 0xe6, 0x7a, 0xb1, 0xd1, 0x78, 0x18, 0xf2, 0x4d, 0xab, 0xd5, 0xb2, 0x9d, 0x3a, 0x1d, + 0x8e, 0xec, 0xc3, 0x85, 0xf2, 0xc4, 0x61, 0xa7, 0x98, 0x5f, 0x17, 0x65, 0x58, 0x41, 0xcd, 0xef, + 0x67, 0x60, 0xbc, 0xe4, 0x58, 0x8d, 0x03, 0xdf, 0xf6, 0x71, 0xdb, 0x41, 0x1f, 0x81, 0x3c, 0xdd, + 0x5d, 0x6a, 0x56, 0x60, 0x89, 0x15, 0xf9, 0xae, 0x45, 0xbe, 0xd8, 0x17, 0xf5, 0xc5, 0x1e, 0xf6, + 0x0b, 0xc5, 0x5e, 0xdc, 0x7f, 0xf7, 0xe2, 0xe5, 0xed, 0x1b, 0xa4, 0x1a, 0xac, 0x93, 0xc0, 0x2a, + 0x23, 0xd1, 0x0a, 0x08, 0xcb, 0xb0, 0xa2, 0x8a, 0x5c, 0x18, 0xf1, 0x5b, 0xa4, 0x2a, 0x56, 0xd8, + 0xfa, 0x90, 0x33, 0x39, 0x14, 0xbd, 0xd2, 0x22, 0xd5, 0xf2, 0x84, 0x60, 0x3d, 0x42, 0xff, 0x61, + 0xc6, 0x08, 0xdd, 0x84, 0x51, 0x9f, 0xed, 0x39, 0x62, 0xf1, 0x5c, 0x4e, 0x8f, 0x25, 0x23, 0x5b, + 0x9e, 0x12, 0x4c, 0x47, 0xf9, 0x7f, 0x2c, 0xd8, 0x99, 0xff, 0x68, 0xc0, 0x29, 0x0d, 0xbb, 0xe4, + 0xd5, 0xdb, 0x4d, 0xe2, 0x04, 0xe8, 0x01, 0x18, 0x71, 0xac, 0x26, 0x11, 0x0b, 0x45, 0x89, 0xbc, + 0x61, 0x35, 0x09, 0x66, 0x10, 0xf4, 0x20, 0xe4, 0xf6, 0xad, 0x46, 0x9b, 0xb0, 0x4e, 0x2a, 0x94, + 0x27, 0x05, 0x4a, 0xee, 0x1a, 0x2d, 0xc4, 0x1c, 0x86, 0x5e, 0x84, 0x02, 0xfb, 0x71, 0xc1, 0x73, + 0x9b, 0x29, 0x35, 0x4d, 0x48, 0x78, 0x4d, 0x92, 0x2d, 0x4f, 0x1e, 0x76, 0x8a, 0x05, 0xf5, 0x17, + 0x87, 0x0c, 0xcd, 0x7f, 0x32, 0x60, 0x5a, 0x6b, 0xdc, 0x9a, 0xed, 0x07, 0xe8, 0x83, 0x5d, 0x93, + 0x67, 0xb1, 0xbf, 0xc9, 0x43, 0x6b, 0xb3, 0xa9, 0x33, 0x23, 0x5a, 0x9a, 0x97, 0x25, 0xda, 0xc4, + 0x71, 0x20, 0x67, 0x07, 0xa4, 0xe9, 0xcf, 0x67, 0x1e, 0xc8, 0x3e, 0x3c, 0xfe, 0xc8, 0x6a, 0x6a, + 0xc3, 0x18, 0xf6, 0xef, 0x2a, 0xa5, 0x8f, 0x39, 0x1b, 0xf3, 0x2b, 0x23, 0x91, 0x16, 0xd2, 0x19, + 0x85, 0x5c, 0x18, 0x6b, 0x92, 0xc0, 0xb3, 0xab, 0x7c, 0x5d, 0x8d, 0x3f, 0xb2, 0x32, 0x9c, 0x14, + 0xeb, 0x8c, 0x58, 0xb8, 0x59, 0xf2, 0xff, 0x3e, 0x96, 0x5c, 0xd0, 0x2e, 0x8c, 0x58, 0x5e, 0x5d, + 0xb6, 0xf9, 0x42, 0x3a, 0xe3, 0x1b, 0xce, 0xb9, 0x92, 0x57, 0xf7, 0x31, 0xe3, 0x80, 0x96, 0xa0, + 0x10, 0x10, 0xaf, 0x69, 0x3b, 0x56, 0xc0, 0x77, 0xd7, 0x7c, 0x79, 0x56, 0xa0, 0x15, 0xb6, 0x24, + 0x00, 0x87, 0x38, 0xa8, 0x01, 0xa3, 0x35, 0xef, 0x00, 0xb7, 0x9d, 0xf9, 0x91, 0x34, 0xba, 0x62, + 0x85, 0xd1, 0x0a, 0x17, 0x13, 0xff, 0x8f, 0x05, 0x0f, 0xf4, 0xba, 0x01, 0x73, 0x4d, 0x62, 0xf9, + 0x6d, 0x8f, 0xd0, 0x26, 0x60, 0x12, 0x10, 0x87, 0xee, 0x86, 0xf3, 0x39, 0xc6, 0x1c, 0x0f, 0x3b, + 0x0e, 0xdd, 0x94, 0xcb, 0xf7, 0x0b, 0x51, 0xe6, 0x92, 0xa0, 0x38, 0x51, 0x1a, 0xf3, 0xfb, 0x23, + 0x30, 0xdb, 0xb5, 0x43, 0xa0, 0xc7, 0x20, 0xd7, 0xda, 0xb5, 0x7c, 0xb9, 0xe4, 0xcf, 0xc9, 0xf9, + 0xb6, 0x49, 0x0b, 0x6f, 0x77, 0x8a, 0x93, 0xb2, 0x0a, 0x2b, 0xc0, 0x1c, 0x99, 0xea, 0xd4, 0x26, + 0xf1, 0x7d, 0xab, 0x2e, 0xf7, 0x01, 0x6d, 0x9a, 0xb0, 0x62, 0x2c, 0xe1, 0xe8, 0x57, 0x0c, 0x98, + 0xe4, 0x53, 0x06, 0x13, 0xbf, 0xdd, 0x08, 0xe8, 0x5e, 0x47, 0xbb, 0xe5, 0x52, 0x1a, 0xd3, 0x93, + 0x93, 0x2c, 0x9f, 0x16, 0xdc, 0x27, 0xf5, 0x52, 0x1f, 0x47, 0xf9, 0xa2, 0xeb, 0x50, 0xf0, 0x03, + 0xcb, 0x0b, 0x48, 0xad, 0x14, 0x30, 0xad, 0x36, 0xfe, 0xc8, 0xff, 0xef, 0x6f, 0x13, 0xd8, 0xb2, + 0x9b, 0x84, 0x6f, 0x38, 0x15, 0x49, 0x00, 0x87, 0xb4, 0xd0, 0x8b, 0x00, 0x5e, 0xdb, 0xa9, 0xb4, + 0x9b, 0x4d, 0xcb, 0x3b, 0x10, 0x1a, 0xfc, 0xe2, 0x70, 0xcd, 0xc3, 0x8a, 0x5e, 0xa8, 0xb3, 0xc2, + 0x32, 0xac, 0xf1, 0x43, 0x2f, 0x1b, 0x30, 0xc9, 0x67, 0xa2, 0x94, 0x60, 0x34, 0x65, 0x09, 0x66, + 0x69, 0xd7, 0xae, 0xe8, 0x2c, 0x70, 0x94, 0xa3, 0xf9, 0xf7, 0x51, 0x7d, 0x52, 0x09, 0xa8, 0x75, + 0x5d, 0x3f, 0x40, 0x1f, 0x80, 0x7b, 0xfd, 0x76, 0xb5, 0x4a, 0x7c, 0x7f, 0xa7, 0xdd, 0xc0, 0x6d, + 0xe7, 0xa2, 0xed, 0x07, 0xae, 0x77, 0xb0, 0x66, 0x37, 0xed, 0x80, 0xcd, 0xb8, 0x5c, 0xf9, 0xec, + 0x61, 0xa7, 0x78, 0x6f, 0xa5, 0x17, 0x12, 0xee, 0x5d, 0x1f, 0x59, 0x70, 0x5f, 0xdb, 0xe9, 0x4d, + 0x9e, 0x5b, 0x6f, 0xc5, 0xc3, 0x4e, 0xf1, 0xbe, 0xab, 0xbd, 0xd1, 0xf0, 0x51, 0x34, 0xcc, 0x7f, + 0x33, 0x60, 0x46, 0xb6, 0x6b, 0x8b, 0x34, 0x5b, 0x0d, 0xba, 0xbb, 0x9c, 0xbc, 0x21, 0x12, 0x44, + 0x0c, 0x11, 0x9c, 0x8e, 0x3a, 0x91, 0xf2, 0xf7, 0xb2, 0x46, 0xcc, 0x7f, 0x35, 0x60, 0x2e, 0x8e, + 0x7c, 0x17, 0x94, 0xa7, 0x1f, 0x55, 0x9e, 0x1b, 0xe9, 0xb6, 0xb6, 0x87, 0x06, 0x7d, 0x75, 0xa4, + 0xbb, 0xad, 0xff, 0xdb, 0xd5, 0x68, 0xa8, 0x15, 0xb3, 0x3f, 0x4d, 0xad, 0x38, 0xf2, 0xa6, 0xd2, + 0x8a, 0xbf, 0x3f, 0x02, 0x13, 0x25, 0x27, 0xb0, 0x4b, 0x3b, 0x3b, 0xb6, 0x63, 0x07, 0x07, 0xe8, + 0xd3, 0x19, 0x58, 0x6a, 0x79, 0x64, 0x87, 0x78, 0x1e, 0xa9, 0xad, 0xb4, 0x3d, 0xdb, 0xa9, 0x57, + 0xaa, 0xbb, 0xa4, 0xd6, 0x6e, 0xd8, 0x4e, 0x7d, 0xb5, 0xee, 0xb8, 0xaa, 0xf8, 0xfc, 0x2d, 0x52, + 0x6d, 0xb3, 0x26, 0xf1, 0x45, 0xd1, 0x1c, 0xae, 0x49, 0x9b, 0x83, 0x31, 0x2d, 0x3f, 0x7a, 0xd8, + 0x29, 0x2e, 0x0d, 0x58, 0x09, 0x0f, 0xda, 0x34, 0xf4, 0xa9, 0x0c, 0x2c, 0x7a, 0xe4, 0xa3, 0x6d, + 0xbb, 0xff, 0xde, 0xe0, 0xbb, 0x56, 0x63, 0x48, 0xf5, 0x33, 0x10, 0xcf, 0xf2, 0x23, 0x87, 0x9d, + 0xe2, 0x80, 0x75, 0xf0, 0x80, 0xed, 0x32, 0xbf, 0x96, 0x81, 0xd3, 0xa5, 0x56, 0x6b, 0x9d, 0xf8, + 0xbb, 0xb1, 0x43, 0xed, 0x67, 0x0d, 0x98, 0xda, 0xb7, 0xbd, 0xa0, 0x6d, 0x35, 0xa4, 0x13, 0x80, + 0x4f, 0x89, 0xca, 0x90, 0xcb, 0x99, 0x73, 0xbb, 0x16, 0x21, 0x5d, 0x46, 0x87, 0x9d, 0xe2, 0x54, + 0xb4, 0x0c, 0xc7, 0xd8, 0xa3, 0xdf, 0x34, 0x60, 0x46, 0x14, 0x6d, 0xb8, 0x35, 0xa2, 0x7b, 0x8e, + 0xae, 0xa6, 0x29, 0x93, 0x22, 0xce, 0x5d, 0x0c, 0xf1, 0x52, 0xdc, 0x25, 0x84, 0xf9, 0x1f, 0x19, + 0x38, 0xd3, 0x83, 0x06, 0xfa, 0x3d, 0x03, 0xe6, 0xb8, 0xbb, 0x49, 0x03, 0x61, 0xb2, 0x23, 0x7a, + 0xf3, 0xfd, 0x69, 0x4b, 0x8e, 0xe9, 0x5a, 0x20, 0x4e, 0x95, 0x94, 0xe7, 0xe9, 0xb6, 0xb1, 0x9c, + 0xc0, 0x1a, 0x27, 0x0a, 0xc4, 0x24, 0xe5, 0x0e, 0xa8, 0x98, 0xa4, 0x99, 0xbb, 0x22, 0x69, 0x25, + 0x81, 0x35, 0x4e, 0x14, 0xc8, 0xfc, 0x45, 0xb8, 0xef, 0x08, 0x72, 0x77, 0x3e, 0xf1, 0x9b, 0xcf, + 0xab, 0x59, 0x1f, 0x9d, 0x73, 0x7d, 0x38, 0x0b, 0x4c, 0x18, 0xf5, 0xdc, 0x76, 0x40, 0xb8, 0x76, + 0x2b, 0x94, 0x81, 0xea, 0x09, 0xcc, 0x4a, 0xb0, 0x80, 0x98, 0x5f, 0x33, 0x20, 0x3f, 0x80, 0xff, + 0xa1, 0x18, 0xf5, 0x3f, 0x14, 0xba, 0x7c, 0x0f, 0x41, 0xb7, 0xef, 0xe1, 0x99, 0xe1, 0x46, 0xa3, + 0x1f, 0x9f, 0xc3, 0x8f, 0x0c, 0x98, 0xed, 0xf2, 0x51, 0xa0, 0x5d, 0x98, 0x6b, 0xb9, 0x35, 0x69, + 0x5f, 0x5c, 0xb4, 0xfc, 0x5d, 0x06, 0x13, 0xcd, 0x7b, 0x8c, 0x8e, 0xe4, 0x66, 0x02, 0xfc, 0x76, + 0xa7, 0x38, 0xaf, 0x88, 0xc4, 0x10, 0x70, 0x22, 0x45, 0xd4, 0x82, 0xfc, 0x8e, 0x4d, 0x1a, 0xb5, + 0x70, 0x0a, 0x0e, 0x69, 0x49, 0x5c, 0x10, 0xd4, 0xb8, 0x7b, 0x4e, 0xfe, 0xc3, 0x8a, 0x8b, 0x79, + 0x05, 0xa6, 0xa2, 0xce, 0xda, 0x3e, 0x06, 0xef, 0x2c, 0x64, 0x2d, 0xcf, 0x11, 0x43, 0x37, 0x2e, + 0x10, 0xb2, 0x25, 0xbc, 0x81, 0x69, 0xb9, 0xf9, 0x93, 0x11, 0x98, 0x2e, 0x37, 0xda, 0xe4, 0x19, + 0x8f, 0x10, 0x79, 0x3e, 0x2d, 0xc1, 0x74, 0xcb, 0x23, 0xfb, 0x36, 0xb9, 0x59, 0x21, 0x0d, 0x52, + 0x0d, 0x5c, 0x4f, 0xd0, 0x3f, 0x23, 0xaa, 0x4f, 0x6f, 0x46, 0xc1, 0x38, 0x8e, 0x8f, 0x9e, 0x86, + 0x29, 0xab, 0x1a, 0xd8, 0xfb, 0x44, 0x51, 0xe0, 0x02, 0xbc, 0x45, 0x50, 0x98, 0x2a, 0x45, 0xa0, + 0x38, 0x86, 0x8d, 0x3e, 0x08, 0xf3, 0x7e, 0xd5, 0x6a, 0x90, 0xab, 0x2d, 0xc1, 0x6a, 0x79, 0x97, + 0x54, 0xf7, 0x36, 0x5d, 0xdb, 0x09, 0x84, 0x37, 0xe2, 0x01, 0x41, 0x69, 0xbe, 0xd2, 0x03, 0x0f, + 0xf7, 0xa4, 0x80, 0xfe, 0xcc, 0x80, 0xb3, 0x2d, 0x8f, 0x6c, 0x7a, 0x6e, 0xd3, 0xa5, 0x6a, 0xa6, + 0xeb, 0x88, 0x2e, 0x8e, 0xaa, 0xd7, 0x86, 0xd4, 0xa7, 0xbc, 0xa4, 0xdb, 0x45, 0xf8, 0xd6, 0xc3, + 0x4e, 0xf1, 0xec, 0xe6, 0x51, 0x02, 0xe0, 0xa3, 0xe5, 0x43, 0x7f, 0x61, 0xc0, 0xb9, 0x96, 0xeb, + 0x07, 0x47, 0x34, 0x21, 0x77, 0xa2, 0x4d, 0x30, 0x0f, 0x3b, 0xc5, 0x73, 0x9b, 0x47, 0x4a, 0x80, + 0xef, 0x20, 0xa1, 0x79, 0x38, 0x0e, 0xb3, 0xda, 0xdc, 0x13, 0xe7, 0xd7, 0x27, 0x61, 0x52, 0x4e, + 0x86, 0x50, 0xad, 0x17, 0x42, 0x7f, 0x43, 0x49, 0x07, 0xe2, 0x28, 0x2e, 0x9d, 0x77, 0x6a, 0x2a, + 0xf2, 0xda, 0xb1, 0x79, 0xb7, 0x19, 0x81, 0xe2, 0x18, 0x36, 0x5a, 0x85, 0x53, 0xa2, 0x04, 0x93, + 0x56, 0xc3, 0xae, 0x5a, 0xcb, 0x6e, 0x5b, 0x4c, 0xb9, 0x5c, 0xf9, 0xcc, 0x61, 0xa7, 0x78, 0x6a, + 0xb3, 0x1b, 0x8c, 0x93, 0xea, 0xa0, 0x35, 0x98, 0xb3, 0xda, 0x81, 0xab, 0xda, 0x7f, 0xde, 0xa1, + 0x9a, 0xa2, 0xc6, 0xa6, 0x56, 0x9e, 0xab, 0x94, 0x52, 0x02, 0x1c, 0x27, 0xd6, 0x42, 0x9b, 0x31, + 0x6a, 0x15, 0x52, 0x75, 0x9d, 0x1a, 0x1f, 0xe5, 0x5c, 0x68, 0x85, 0x97, 0x12, 0x70, 0x70, 0x62, + 0x4d, 0xd4, 0x80, 0xa9, 0xa6, 0x75, 0xeb, 0xaa, 0x63, 0xed, 0x5b, 0x76, 0x83, 0x32, 0x11, 0x3e, + 0x8c, 0xde, 0x07, 0xeb, 0x76, 0x60, 0x37, 0x16, 0xf9, 0x75, 0xde, 0xe2, 0xaa, 0x13, 0x5c, 0xf6, + 0x2a, 0x01, 0xb5, 0xd6, 0xb8, 0x71, 0xb4, 0x1e, 0xa1, 0x85, 0x63, 0xb4, 0xd1, 0x65, 0x38, 0xcd, + 0x96, 0xe3, 0x8a, 0x7b, 0xd3, 0x59, 0x21, 0x0d, 0xeb, 0x40, 0x36, 0x60, 0x8c, 0x35, 0xe0, 0xde, + 0xc3, 0x4e, 0xf1, 0x74, 0x25, 0x09, 0x01, 0x27, 0xd7, 0x43, 0x16, 0xdc, 0x17, 0x05, 0x60, 0xb2, + 0x6f, 0xfb, 0xb6, 0xeb, 0x70, 0x4f, 0x44, 0x3e, 0xf4, 0x44, 0x54, 0x7a, 0xa3, 0xe1, 0xa3, 0x68, + 0xa0, 0xdf, 0x36, 0x60, 0x2e, 0x69, 0x19, 0xce, 0x17, 0xd2, 0xb8, 0xac, 0x88, 0x2d, 0x2d, 0x3e, + 0x23, 0x12, 0x37, 0x85, 0x44, 0x21, 0xd0, 0x4b, 0x06, 0x4c, 0x58, 0xda, 0x29, 0x6a, 0x1e, 0x98, + 0x54, 0x97, 0x86, 0x3d, 0xcb, 0x87, 0x14, 0xcb, 0x33, 0x87, 0x9d, 0x62, 0xe4, 0xa4, 0x86, 0x23, + 0x1c, 0xd1, 0xef, 0x18, 0x70, 0x3a, 0x71, 0x8d, 0xcf, 0x8f, 0x9f, 0x44, 0x0f, 0xb1, 0x49, 0x92, + 0xbc, 0xe7, 0x24, 0x8b, 0x81, 0x5e, 0x33, 0x94, 0x2a, 0x5b, 0x97, 0xde, 0x94, 0x09, 0x26, 0xda, + 0x95, 0x21, 0x0f, 0x8e, 0xa1, 0x41, 0x20, 0x09, 0x97, 0x4f, 0x69, 0x9a, 0x51, 0x16, 0xe2, 0x38, + 0x7b, 0xf4, 0x19, 0x43, 0xaa, 0x46, 0x25, 0xd1, 0xe4, 0x49, 0x49, 0x84, 0x42, 0x4d, 0xab, 0x04, + 0x8a, 0x31, 0x47, 0x1f, 0x82, 0x05, 0x6b, 0xdb, 0xf5, 0x82, 0xc4, 0xc5, 0x37, 0x3f, 0xc5, 0x96, + 0xd1, 0xb9, 0xc3, 0x4e, 0x71, 0xa1, 0xd4, 0x13, 0x0b, 0x1f, 0x41, 0xc1, 0xfc, 0xa3, 0x1c, 0x4c, + 0x70, 0x23, 0x5f, 0xa8, 0xae, 0xaf, 0x1a, 0x70, 0x7f, 0xb5, 0xed, 0x79, 0xc4, 0x09, 0x2a, 0x01, + 0x69, 0x75, 0x2b, 0x2e, 0xe3, 0x44, 0x15, 0xd7, 0x03, 0x87, 0x9d, 0xe2, 0xfd, 0xcb, 0x47, 0xf0, + 0xc7, 0x47, 0x4a, 0x87, 0xfe, 0xc6, 0x00, 0x53, 0x20, 0x94, 0xad, 0xea, 0x5e, 0xdd, 0x73, 0xdb, + 0x4e, 0xad, 0xbb, 0x11, 0x99, 0x13, 0x6d, 0xc4, 0x43, 0x87, 0x9d, 0xa2, 0xb9, 0x7c, 0x47, 0x29, + 0x70, 0x1f, 0x92, 0xa2, 0x67, 0x60, 0x56, 0x60, 0x9d, 0xbf, 0xd5, 0x22, 0x9e, 0x4d, 0xcd, 0x69, + 0x71, 0x9f, 0x1e, 0x86, 0x28, 0xc4, 0x11, 0x70, 0x77, 0x1d, 0xe4, 0xc3, 0xd8, 0x4d, 0x62, 0xd7, + 0x77, 0x03, 0x69, 0x3e, 0x0d, 0x19, 0x97, 0x20, 0x0e, 0xfc, 0xd7, 0x39, 0xcd, 0xf2, 0xf8, 0x61, + 0xa7, 0x38, 0x26, 0xfe, 0x60, 0xc9, 0x09, 0x6d, 0xc0, 0x14, 0x3f, 0x82, 0x6d, 0xda, 0x4e, 0x7d, + 0xd3, 0x75, 0xf8, 0x6d, 0x7e, 0xa1, 0xfc, 0x90, 0x54, 0xf8, 0x95, 0x08, 0xf4, 0x76, 0xa7, 0x38, + 0x21, 0x7f, 0x6f, 0x1d, 0xb4, 0x08, 0x8e, 0xd5, 0x36, 0xbf, 0x39, 0x0a, 0x20, 0xa7, 0x2b, 0x69, + 0xa1, 0x9f, 0x83, 0x82, 0x4f, 0x02, 0xce, 0x55, 0x38, 0xcf, 0xf9, 0x9d, 0x84, 0x2c, 0xc4, 0x21, + 0x1c, 0xed, 0x41, 0xae, 0x65, 0xb5, 0x7d, 0x22, 0x06, 0xff, 0x52, 0x2a, 0x83, 0xbf, 0x49, 0x29, + 0xf2, 0x33, 0x17, 0xfb, 0x89, 0x39, 0x0f, 0xf4, 0x49, 0x03, 0x80, 0x44, 0x07, 0x6c, 0x68, 0xdf, + 0x87, 0x60, 0x19, 0x8e, 0x29, 0xed, 0x83, 0xf2, 0xd4, 0x61, 0xa7, 0x08, 0xda, 0xd0, 0x6b, 0x6c, + 0xd1, 0x4d, 0xc8, 0x5b, 0x72, 0xcf, 0x1f, 0x39, 0x89, 0x3d, 0x9f, 0x1d, 0x85, 0xd4, 0xa4, 0x55, + 0xcc, 0xd0, 0xa7, 0x0c, 0x98, 0xf2, 0x49, 0x20, 0x86, 0x8a, 0xee, 0x3c, 0xc2, 0xe0, 0x1d, 0x72, + 0xd2, 0x55, 0x22, 0x34, 0xf9, 0x0e, 0x1a, 0x2d, 0xc3, 0x31, 0xbe, 0x52, 0x94, 0x8b, 0xc4, 0xaa, + 0x11, 0x8f, 0x9d, 0xb4, 0x85, 0x25, 0x35, 0xbc, 0x28, 0x1a, 0x4d, 0x25, 0x8a, 0x56, 0x86, 0x63, + 0x7c, 0xa5, 0x28, 0xeb, 0xb6, 0xe7, 0xb9, 0x42, 0x94, 0x7c, 0x4a, 0xa2, 0x68, 0x34, 0x95, 0x28, + 0x5a, 0x19, 0x8e, 0xf1, 0x35, 0xff, 0x76, 0x02, 0xa6, 0xe4, 0x42, 0x0a, 0x2d, 0x7b, 0xee, 0xd8, + 0xe9, 0x61, 0xd9, 0x2f, 0xeb, 0x40, 0x1c, 0xc5, 0xa5, 0x95, 0xf9, 0x52, 0x8d, 0x1a, 0xf6, 0xaa, + 0x72, 0x45, 0x07, 0xe2, 0x28, 0x2e, 0x6a, 0x42, 0xce, 0x0f, 0x48, 0x4b, 0xde, 0x83, 0x0e, 0x79, + 0x4d, 0x17, 0xee, 0x0f, 0xe1, 0x4d, 0x07, 0xfd, 0xe7, 0x63, 0xce, 0x85, 0xf9, 0x26, 0x83, 0x88, + 0xbb, 0x52, 0x2c, 0x8e, 0x74, 0xd6, 0x67, 0xd4, 0x13, 0xca, 0x47, 0x23, 0x5a, 0x86, 0x63, 0xec, + 0x13, 0x8c, 0xfd, 0xdc, 0x09, 0x1a, 0xfb, 0xcf, 0x41, 0xbe, 0x69, 0xdd, 0xaa, 0xb4, 0xbd, 0xfa, + 0xf1, 0x0f, 0x15, 0x22, 0x44, 0x89, 0x53, 0xc1, 0x8a, 0x1e, 0x7a, 0xd9, 0xd0, 0xb6, 0x9c, 0x31, + 0x46, 0xfc, 0x7a, 0xba, 0x5b, 0x8e, 0xd2, 0x95, 0x3d, 0x37, 0x9f, 0x2e, 0xd3, 0x3b, 0x7f, 0xd7, + 0x4d, 0x6f, 0x6a, 0x46, 0xf2, 0x05, 0xa2, 0xcc, 0xc8, 0xc2, 0x89, 0x9a, 0x91, 0xcb, 0x11, 0x66, + 0x38, 0xc6, 0x9c, 0xc9, 0xc3, 0xd7, 0x9c, 0x92, 0x07, 0x4e, 0x54, 0x9e, 0x4a, 0x84, 0x19, 0x8e, + 0x31, 0xef, 0x7d, 0xde, 0x1c, 0x3f, 0x99, 0xf3, 0xe6, 0x44, 0x0a, 0xe7, 0xcd, 0xa3, 0x4d, 0xf1, + 0xc9, 0x61, 0x4d, 0x71, 0x74, 0x09, 0x50, 0xed, 0xc0, 0xb1, 0x9a, 0x76, 0x55, 0x6c, 0x96, 0x4c, + 0x6d, 0x4e, 0x31, 0x7f, 0xc4, 0x82, 0xd8, 0xc8, 0xd0, 0x4a, 0x17, 0x06, 0x4e, 0xa8, 0x85, 0x02, + 0xc8, 0xb7, 0xa4, 0xc5, 0x35, 0x9d, 0xc6, 0xec, 0x97, 0x16, 0x18, 0xbf, 0x2a, 0xa7, 0x0b, 0x4f, + 0x96, 0x60, 0xc5, 0xc9, 0xfc, 0x2f, 0x03, 0x66, 0x96, 0x1b, 0x6e, 0xbb, 0x76, 0xdd, 0x0a, 0xaa, + 0xbb, 0xfc, 0x5e, 0x17, 0x3d, 0x0d, 0x79, 0xdb, 0x09, 0x88, 0xb7, 0x6f, 0x35, 0x84, 0x46, 0x31, + 0xe5, 0xd5, 0xf7, 0xaa, 0x28, 0xbf, 0xdd, 0x29, 0x4e, 0xad, 0xb4, 0x3d, 0x16, 0x30, 0xc9, 0xf7, + 0x17, 0xac, 0xea, 0xa0, 0x2f, 0x19, 0x30, 0xcb, 0x6f, 0x86, 0x57, 0xac, 0xc0, 0xba, 0xd2, 0x26, + 0x9e, 0x4d, 0xe4, 0xdd, 0xf0, 0x90, 0x5b, 0x4b, 0x5c, 0x56, 0xc9, 0xe0, 0x20, 0x34, 0xad, 0xd7, + 0xe3, 0x9c, 0x71, 0xb7, 0x30, 0xe6, 0xe7, 0xb2, 0x70, 0x6f, 0x4f, 0x5a, 0x68, 0x01, 0x32, 0x76, + 0x4d, 0x34, 0x1d, 0x04, 0xdd, 0xcc, 0x6a, 0x0d, 0x67, 0xec, 0x1a, 0x5a, 0x64, 0x56, 0xa2, 0x47, + 0x7c, 0x5f, 0x5e, 0x13, 0x16, 0x94, 0x41, 0x27, 0x4a, 0xb1, 0x86, 0x81, 0x8a, 0x90, 0x6b, 0x58, + 0xdb, 0xa4, 0x21, 0x4e, 0x00, 0xcc, 0xee, 0x5c, 0xa3, 0x05, 0x98, 0x97, 0xa3, 0x5f, 0x36, 0x00, + 0xb8, 0x80, 0xf4, 0xfc, 0x20, 0xf4, 0x1a, 0x4e, 0xb7, 0x9b, 0x28, 0x65, 0x2e, 0x65, 0xf8, 0x1f, + 0x6b, 0x5c, 0xd1, 0x16, 0x8c, 0x52, 0x13, 0xd4, 0xad, 0x1d, 0x5b, 0x8d, 0xb1, 0x6b, 0x91, 0x4d, + 0x46, 0x03, 0x0b, 0x5a, 0xb4, 0xaf, 0x3c, 0x12, 0xb4, 0x3d, 0x87, 0x76, 0x2d, 0x53, 0x5c, 0x79, + 0x2e, 0x05, 0x56, 0xa5, 0x58, 0xc3, 0x30, 0xff, 0x34, 0x03, 0x73, 0x49, 0xa2, 0x53, 0xfd, 0x30, + 0xca, 0xa5, 0x15, 0x87, 0xd9, 0xf7, 0xa5, 0xdf, 0x3f, 0x22, 0xc8, 0x41, 0x85, 0x02, 0x88, 0x30, + 0x2c, 0xc1, 0x17, 0xbd, 0x4f, 0xf5, 0x50, 0xe6, 0x98, 0x3d, 0xa4, 0x28, 0xc7, 0x7a, 0xe9, 0x01, + 0x18, 0xf1, 0xe9, 0xc8, 0x67, 0xa3, 0x57, 0x0e, 0x6c, 0x8c, 0x18, 0x84, 0x62, 0xb4, 0x1d, 0x3b, + 0x10, 0x51, 0xcc, 0x0a, 0xe3, 0xaa, 0x63, 0x07, 0x98, 0x41, 0xcc, 0x2f, 0x64, 0x60, 0xa1, 0x77, + 0xa3, 0xd0, 0x17, 0x0c, 0x80, 0x1a, 0x3d, 0x60, 0xd0, 0x29, 0x29, 0x83, 0x42, 0xac, 0x93, 0xea, + 0xc3, 0x15, 0xc9, 0x29, 0x8c, 0x10, 0x52, 0x45, 0x3e, 0xd6, 0x04, 0x41, 0x8f, 0xc8, 0xa9, 0xbf, + 0x61, 0x35, 0xa5, 0x01, 0xaa, 0xea, 0xac, 0x2b, 0x08, 0xd6, 0xb0, 0xe8, 0x09, 0xd2, 0xb1, 0x9a, + 0xc4, 0x6f, 0x59, 0x2a, 0x4c, 0x9d, 0x9d, 0x20, 0x37, 0x64, 0x21, 0x0e, 0xe1, 0x66, 0x03, 0x1e, + 0xec, 0x43, 0xce, 0x94, 0x42, 0x86, 0xcd, 0xff, 0x34, 0xe0, 0xcc, 0x72, 0xa3, 0xed, 0x07, 0xc4, + 0xfb, 0x3f, 0x13, 0x70, 0xf5, 0xdf, 0x06, 0xdc, 0xd7, 0xa3, 0xcd, 0x77, 0x21, 0xee, 0xea, 0x85, + 0x68, 0xdc, 0xd5, 0xd5, 0x61, 0xa7, 0x74, 0x62, 0x3b, 0x7a, 0x84, 0x5f, 0x05, 0x30, 0x49, 0x77, + 0xad, 0x9a, 0x5b, 0x4f, 0x49, 0x6f, 0x3e, 0x08, 0xb9, 0x8f, 0x52, 0xfd, 0x13, 0x9f, 0x63, 0x4c, + 0x29, 0x61, 0x0e, 0x33, 0x9f, 0x02, 0x11, 0xa4, 0x14, 0x5b, 0x3c, 0x46, 0x3f, 0x8b, 0xc7, 0xfc, + 0x87, 0x0c, 0x68, 0x9e, 0x87, 0xbb, 0x30, 0x29, 0x9d, 0xc8, 0xa4, 0x1c, 0xf2, 0xd4, 0xac, 0xf9, + 0x51, 0x7a, 0xbd, 0x46, 0xd8, 0x8f, 0xbd, 0x46, 0xd8, 0x48, 0x8d, 0xe3, 0xd1, 0x8f, 0x11, 0xbe, + 0x6b, 0xc0, 0x7d, 0x21, 0x72, 0xb7, 0x53, 0xf0, 0xce, 0x3b, 0xcc, 0xe3, 0x30, 0x6e, 0x85, 0xd5, + 0xc4, 0x1c, 0x50, 0x0f, 0x70, 0x34, 0x8a, 0x58, 0xc7, 0x0b, 0x63, 0x9f, 0xb3, 0xc7, 0x8c, 0x7d, + 0x1e, 0x39, 0x3a, 0xf6, 0xd9, 0xfc, 0x71, 0x06, 0xce, 0x76, 0xb7, 0x4c, 0xae, 0x8d, 0xfe, 0xee, + 0xcc, 0x9f, 0x80, 0x89, 0x40, 0x54, 0xd0, 0x76, 0x7a, 0xf5, 0x7c, 0x6c, 0x4b, 0x83, 0xe1, 0x08, + 0x26, 0xad, 0x59, 0xe5, 0xab, 0xb2, 0x52, 0x75, 0x5b, 0x32, 0x72, 0x5e, 0xd5, 0x5c, 0xd6, 0x60, + 0x38, 0x82, 0xa9, 0x62, 0x12, 0x47, 0x4e, 0x3c, 0x26, 0xb1, 0x02, 0xa7, 0x65, 0x14, 0xd6, 0x05, + 0xd7, 0x5b, 0x76, 0x9b, 0xad, 0x06, 0x11, 0xb1, 0xf3, 0x54, 0xd8, 0xb3, 0xa2, 0xca, 0x69, 0x9c, + 0x84, 0x84, 0x93, 0xeb, 0x9a, 0xdf, 0xcd, 0xc2, 0xa9, 0xb0, 0xdb, 0x97, 0x5d, 0xa7, 0x66, 0xb3, + 0x58, 0xb6, 0x27, 0x61, 0x24, 0x38, 0x68, 0xc9, 0xce, 0xfe, 0x7f, 0x52, 0x9c, 0xad, 0x83, 0x16, + 0x1d, 0xed, 0x33, 0x09, 0x55, 0x98, 0x5b, 0x96, 0x55, 0x42, 0x6b, 0x6a, 0x75, 0xf0, 0x11, 0x78, + 0x2c, 0x3a, 0x9b, 0x6f, 0x77, 0x8a, 0x09, 0xaf, 0x27, 0x17, 0x15, 0xa5, 0xe8, 0x9c, 0x47, 0x37, + 0x60, 0xaa, 0x61, 0xf9, 0xc1, 0xd5, 0x56, 0xcd, 0x0a, 0xc8, 0x96, 0xdd, 0x24, 0x62, 0xcd, 0x0d, + 0x12, 0x90, 0xae, 0xee, 0x91, 0xd7, 0x22, 0x94, 0x70, 0x8c, 0x32, 0xda, 0x07, 0x44, 0x4b, 0xb6, + 0x3c, 0xcb, 0xf1, 0x79, 0xab, 0x28, 0xbf, 0xc1, 0x03, 0xe0, 0xd5, 0xb1, 0x6c, 0xad, 0x8b, 0x1a, + 0x4e, 0xe0, 0x80, 0x1e, 0x82, 0x51, 0x8f, 0x58, 0xbe, 0x18, 0xcc, 0x42, 0xb8, 0xfe, 0x31, 0x2b, + 0xc5, 0x02, 0xaa, 0x2f, 0xa8, 0xd1, 0x3b, 0x2c, 0xa8, 0x1f, 0x18, 0x30, 0x15, 0x0e, 0xd3, 0x5d, + 0x50, 0x92, 0xcd, 0xa8, 0x92, 0xbc, 0x98, 0xd6, 0x96, 0xd8, 0x43, 0x2f, 0xfe, 0xe5, 0xa8, 0xde, + 0x3e, 0x16, 0x90, 0xfc, 0x31, 0x28, 0xc8, 0x55, 0x2d, 0xad, 0xcf, 0x21, 0x4f, 0xb7, 0x11, 0xbb, + 0x44, 0x7b, 0x48, 0x23, 0x98, 0xe0, 0x90, 0x1f, 0x55, 0xcb, 0x35, 0xa1, 0x72, 0xc5, 0xb4, 0x57, + 0x6a, 0x59, 0xaa, 0xe2, 0x24, 0xb5, 0x2c, 0xeb, 0xa0, 0xab, 0x70, 0xa6, 0xe5, 0xb9, 0xec, 0x71, + 0xe5, 0x0a, 0xb1, 0x6a, 0x0d, 0xdb, 0x21, 0xd2, 0x85, 0xc0, 0xc3, 0x18, 0xee, 0x3b, 0xec, 0x14, + 0xcf, 0x6c, 0x26, 0xa3, 0xe0, 0x5e, 0x75, 0xa3, 0x0f, 0x82, 0x46, 0xfa, 0x78, 0x10, 0xf4, 0xab, + 0xca, 0x51, 0x47, 0x7c, 0xf1, 0x2c, 0xe7, 0x03, 0x69, 0x0d, 0x65, 0xc2, 0xb6, 0x1e, 0x4e, 0xa9, + 0x92, 0x60, 0x8a, 0x15, 0xfb, 0xde, 0xde, 0xa0, 0xd1, 0x63, 0x7a, 0x83, 0xc2, 0xb8, 0xee, 0xb1, + 0x9f, 0x66, 0x5c, 0x77, 0xfe, 0x4d, 0x15, 0xd7, 0xfd, 0x4a, 0x0e, 0x66, 0xe2, 0x16, 0xc8, 0xc9, + 0x3f, 0x76, 0xfa, 0x0d, 0x03, 0x66, 0xe4, 0xea, 0xe1, 0x3c, 0x89, 0xf4, 0xf3, 0xaf, 0xa5, 0xb4, + 0x68, 0xb9, 0x2d, 0xa5, 0x9e, 0xe3, 0x6e, 0xc5, 0xb8, 0xe1, 0x2e, 0xfe, 0xe8, 0x79, 0x18, 0x57, + 0xee, 0xf0, 0x63, 0xbd, 0x7c, 0x9a, 0x66, 0x56, 0x54, 0x48, 0x02, 0xeb, 0xf4, 0xd0, 0x2b, 0x06, + 0x40, 0x55, 0xaa, 0x39, 0xb9, 0xba, 0xae, 0xa4, 0xb5, 0xba, 0x94, 0x02, 0x0d, 0x8d, 0x65, 0x55, + 0xe4, 0x63, 0x8d, 0x31, 0xfa, 0x1c, 0x73, 0x84, 0x2b, 0xeb, 0x8e, 0xae, 0xa7, 0xec, 0xf0, 0xa1, + 0xb8, 0x47, 0x18, 0xa6, 0xa1, 0x29, 0xa5, 0x81, 0x7c, 0x1c, 0x11, 0xc2, 0x7c, 0x12, 0x54, 0xf0, + 0x24, 0xdd, 0xb6, 0x58, 0xf8, 0xe4, 0xa6, 0x15, 0xec, 0x8a, 0x29, 0xa8, 0xb6, 0xad, 0x0b, 0x12, + 0x80, 0x43, 0x1c, 0xf3, 0x23, 0x30, 0xf5, 0x8c, 0x67, 0xb5, 0x76, 0x6d, 0xe6, 0x70, 0xa6, 0xe7, + 0xa4, 0xb7, 0xc3, 0x98, 0x55, 0xab, 0x25, 0x3d, 0x66, 0x2f, 0xf1, 0x62, 0x2c, 0xe1, 0xfd, 0x1d, + 0x89, 0xbe, 0x69, 0x00, 0x0a, 0x2f, 0xed, 0x6c, 0xa7, 0xbe, 0x4e, 0x4f, 0xfb, 0xf4, 0x7c, 0xb4, + 0xcb, 0x4a, 0x93, 0xce, 0x47, 0x17, 0x15, 0x04, 0x6b, 0x58, 0xe8, 0x45, 0x18, 0xe7, 0xff, 0xae, + 0xa9, 0xc3, 0xfe, 0xd0, 0x4f, 0x61, 0xb9, 0x42, 0x61, 0x32, 0xf1, 0x59, 0x78, 0x31, 0xe4, 0x80, + 0x75, 0x76, 0xb4, 0xab, 0x56, 0x9d, 0x9d, 0x46, 0xfb, 0x56, 0x6d, 0x3b, 0xec, 0xaa, 0x96, 0xe7, + 0xee, 0xd8, 0x0d, 0x12, 0xef, 0xaa, 0x4d, 0x5e, 0x8c, 0x25, 0xbc, 0xbf, 0xae, 0xfa, 0xba, 0x01, + 0x73, 0xab, 0x7e, 0x60, 0xbb, 0x2b, 0xc4, 0x0f, 0xa8, 0x5a, 0xa1, 0x9b, 0x4f, 0xbb, 0xd1, 0x4f, + 0x1c, 0xf4, 0x0a, 0xcc, 0x88, 0x0b, 0xc4, 0xf6, 0xb6, 0x4f, 0x02, 0xcd, 0x8e, 0x57, 0xeb, 0x78, + 0x39, 0x06, 0xc7, 0x5d, 0x35, 0x28, 0x15, 0x71, 0x93, 0x18, 0x52, 0xc9, 0x46, 0xa9, 0x54, 0x62, + 0x70, 0xdc, 0x55, 0xc3, 0xfc, 0x76, 0x16, 0x4e, 0xb1, 0x66, 0xc4, 0xde, 0x30, 0x7c, 0xa6, 0xd7, + 0x1b, 0x86, 0x21, 0x97, 0x32, 0xe3, 0x75, 0x8c, 0x17, 0x0c, 0xbf, 0x6e, 0xc0, 0x74, 0x2d, 0xda, + 0xd3, 0xe9, 0xb8, 0x67, 0x92, 0xc6, 0x90, 0xc7, 0x4b, 0xc5, 0x0a, 0x71, 0x9c, 0x3f, 0xfa, 0xbc, + 0x01, 0xd3, 0x51, 0x31, 0xe5, 0xee, 0x7e, 0x02, 0x9d, 0xa4, 0x02, 0x9c, 0xa3, 0xe5, 0x3e, 0x8e, + 0x8b, 0x60, 0xfe, 0x9d, 0x21, 0x86, 0xf4, 0x24, 0x02, 0xf4, 0xd1, 0x4d, 0x28, 0x04, 0x0d, 0x9f, + 0x17, 0x8a, 0xd6, 0x0e, 0x79, 0x22, 0xdc, 0x5a, 0xab, 0xf0, 0xbb, 0xfb, 0xd0, 0x68, 0x13, 0x25, + 0xd4, 0xf8, 0x94, 0xbc, 0xcc, 0x2f, 0x1b, 0x50, 0xb8, 0xe4, 0xca, 0xe5, 0xfc, 0xa1, 0x14, 0xfc, + 0x2d, 0xca, 0x2c, 0x53, 0x57, 0x75, 0xa1, 0xa5, 0xff, 0x74, 0xc4, 0xdb, 0x72, 0xbf, 0x46, 0x7b, + 0x91, 0xe5, 0xcb, 0xa1, 0xa4, 0x2e, 0xb9, 0xdb, 0x3d, 0x9d, 0x79, 0xbf, 0x9b, 0x83, 0xc9, 0x67, + 0xad, 0x03, 0xe2, 0x04, 0xd6, 0xe0, 0x7b, 0xf5, 0xe3, 0x30, 0x6e, 0xb5, 0x58, 0xbc, 0xae, 0x66, + 0x6a, 0x87, 0x0e, 0x8c, 0x10, 0x84, 0x75, 0xbc, 0x70, 0x5f, 0xe1, 0xe9, 0x3b, 0x92, 0x76, 0x84, + 0xe5, 0x18, 0x1c, 0x77, 0xd5, 0x40, 0x97, 0x00, 0x89, 0xc7, 0x88, 0xa5, 0x6a, 0xd5, 0x6d, 0x3b, + 0x7c, 0x67, 0xe1, 0xbe, 0x0d, 0x75, 0xe6, 0x5b, 0xef, 0xc2, 0xc0, 0x09, 0xb5, 0xd0, 0x07, 0x61, + 0xbe, 0xca, 0x28, 0x8b, 0x13, 0x80, 0x4e, 0x91, 0x9f, 0x02, 0x55, 0xac, 0xfc, 0x72, 0x0f, 0x3c, + 0xdc, 0x93, 0x02, 0x95, 0xd4, 0x0f, 0x5c, 0xcf, 0xaa, 0x13, 0x9d, 0xee, 0x68, 0x54, 0xd2, 0x4a, + 0x17, 0x06, 0x4e, 0xa8, 0x85, 0x3e, 0x01, 0x85, 0x60, 0xd7, 0x23, 0xfe, 0xae, 0xdb, 0xa8, 0x89, + 0xbb, 0xfb, 0x21, 0x1d, 0x5e, 0x62, 0xf4, 0xb7, 0x24, 0x55, 0x6d, 0x7a, 0xcb, 0x22, 0x1c, 0xf2, + 0x44, 0x1e, 0x8c, 0xfa, 0x55, 0xb7, 0x45, 0x7c, 0x61, 0x39, 0x5f, 0x4a, 0x85, 0x3b, 0x73, 0xe0, + 0x68, 0xae, 0x36, 0xc6, 0x01, 0x0b, 0x4e, 0xe6, 0x37, 0x32, 0x30, 0xa1, 0x23, 0xf6, 0xb1, 0x45, + 0x7c, 0xd2, 0x80, 0x89, 0xaa, 0xeb, 0x04, 0x9e, 0xdb, 0xe0, 0x6e, 0xa4, 0x74, 0x14, 0x3b, 0x25, + 0xb5, 0x42, 0x02, 0xcb, 0x6e, 0x68, 0x1e, 0x29, 0x8d, 0x0d, 0x8e, 0x30, 0x45, 0x9f, 0x36, 0x60, + 0x3a, 0x0c, 0xf5, 0x0a, 0xfd, 0x59, 0xa9, 0x0a, 0xa2, 0x76, 0xdc, 0xf3, 0x51, 0x4e, 0x38, 0xce, + 0xda, 0xdc, 0x86, 0x99, 0xf8, 0x68, 0xd3, 0xae, 0x6c, 0x59, 0x62, 0xad, 0x67, 0xc3, 0xae, 0xdc, + 0xb4, 0x7c, 0x1f, 0x33, 0x08, 0x7a, 0x07, 0xe4, 0x9b, 0x96, 0x57, 0xb7, 0x1d, 0xab, 0xc1, 0x7a, + 0x31, 0xab, 0x6d, 0x48, 0xa2, 0x1c, 0x2b, 0x0c, 0xf3, 0x5d, 0x30, 0xb1, 0x6e, 0x39, 0x75, 0x52, + 0xe3, 0xdb, 0x61, 0x1f, 0x2f, 0xb5, 0x7e, 0x38, 0x02, 0xe3, 0xda, 0x11, 0xe9, 0xe4, 0x8f, 0x3b, + 0x91, 0x8c, 0x0a, 0xd9, 0x14, 0x33, 0x2a, 0x3c, 0x07, 0xb0, 0x63, 0x3b, 0xb6, 0xbf, 0x7b, 0xcc, + 0x5c, 0x0d, 0xec, 0xa6, 0xf4, 0x82, 0xa2, 0x80, 0x35, 0x6a, 0xe1, 0x75, 0x54, 0xee, 0x88, 0x0c, + 0x36, 0xaf, 0x18, 0x9a, 0xba, 0x19, 0x4d, 0xe3, 0xfa, 0x5d, 0x1b, 0x98, 0x45, 0xa9, 0x7e, 0xce, + 0x3b, 0x81, 0x77, 0x70, 0xa4, 0x56, 0xda, 0x82, 0xbc, 0x47, 0xfc, 0x76, 0x93, 0x1e, 0xdc, 0xc6, + 0x06, 0xee, 0x06, 0x16, 0xba, 0x80, 0x45, 0x7d, 0xac, 0x28, 0x2d, 0x3c, 0x09, 0x93, 0x11, 0x11, + 0xd0, 0x0c, 0x64, 0xf7, 0xc8, 0x01, 0x9f, 0x27, 0x98, 0xfe, 0x44, 0x73, 0x91, 0x4b, 0x3b, 0xd1, + 0x2d, 0xef, 0xcd, 0x3c, 0x61, 0x98, 0x2e, 0x24, 0x9e, 0xc3, 0x8f, 0x73, 0xa7, 0x42, 0xc7, 0xa2, + 0xa1, 0x25, 0x6b, 0x50, 0x63, 0xc1, 0x03, 0x54, 0x38, 0xcc, 0xfc, 0xf1, 0x28, 0x88, 0x1b, 0xe5, + 0x3e, 0xb6, 0x2b, 0xfd, 0x22, 0x29, 0x73, 0x8c, 0x8b, 0xa4, 0x4b, 0x30, 0x61, 0x3b, 0x76, 0x60, + 0x5b, 0x0d, 0xe6, 0x63, 0x11, 0xea, 0x54, 0x46, 0xf0, 0x4e, 0xac, 0x6a, 0xb0, 0x04, 0x3a, 0x91, + 0xba, 0xe8, 0x0a, 0xe4, 0x98, 0xbe, 0x11, 0x13, 0x78, 0xf0, 0x6b, 0x6f, 0x16, 0xf1, 0xc0, 0x9f, + 0xf5, 0x70, 0x4a, 0xec, 0x0c, 0xc0, 0xb3, 0x55, 0xa8, 0x53, 0xb0, 0x98, 0xc7, 0xe1, 0x19, 0x20, + 0x06, 0xc7, 0x5d, 0x35, 0x28, 0x95, 0x1d, 0xcb, 0x6e, 0xb4, 0x3d, 0x12, 0x52, 0x19, 0x8d, 0x52, + 0xb9, 0x10, 0x83, 0xe3, 0xae, 0x1a, 0x68, 0x07, 0x26, 0x44, 0x19, 0x0f, 0x3b, 0x1a, 0x3b, 0x66, + 0x2b, 0x59, 0x78, 0xd9, 0x05, 0x8d, 0x12, 0x8e, 0xd0, 0x45, 0x6d, 0x98, 0xb5, 0x9d, 0xaa, 0xeb, + 0x54, 0x1b, 0x6d, 0xdf, 0xde, 0x27, 0xe1, 0x9b, 0x9a, 0xe3, 0x30, 0x3b, 0x7d, 0xd8, 0x29, 0xce, + 0xae, 0xc6, 0xc9, 0xe1, 0x6e, 0x0e, 0xe8, 0x65, 0x03, 0x4e, 0x57, 0x5d, 0xc7, 0x67, 0xcf, 0xbf, + 0xf7, 0xc9, 0x79, 0xcf, 0x73, 0x3d, 0xce, 0xbb, 0x70, 0x4c, 0xde, 0xcc, 0xb5, 0xb7, 0x9c, 0x44, + 0x12, 0x27, 0x73, 0x42, 0x2f, 0x40, 0xbe, 0xe5, 0xb9, 0xfb, 0x76, 0x8d, 0x78, 0x22, 0x84, 0x6d, + 0x2d, 0x8d, 0x74, 0x14, 0x9b, 0x82, 0x66, 0xb8, 0xf5, 0xc8, 0x12, 0xac, 0xf8, 0x99, 0xaf, 0x17, + 0x60, 0x2a, 0x8a, 0x8e, 0x3e, 0x0e, 0xd0, 0xf2, 0xdc, 0x26, 0x09, 0x76, 0x89, 0x7a, 0x1b, 0xb1, + 0x31, 0x6c, 0xd6, 0x03, 0x49, 0x4f, 0x06, 0x91, 0xd0, 0xed, 0x22, 0x2c, 0xc5, 0x1a, 0x47, 0xe4, + 0xc1, 0xd8, 0x1e, 0x57, 0xbb, 0xc2, 0x0a, 0x79, 0x36, 0x15, 0x9b, 0x49, 0x70, 0x66, 0x41, 0xfd, + 0xa2, 0x08, 0x4b, 0x46, 0x68, 0x1b, 0xb2, 0x37, 0xc9, 0x76, 0x3a, 0x2f, 0x89, 0xaf, 0x13, 0x71, + 0x9a, 0x29, 0x8f, 0x1d, 0x76, 0x8a, 0xd9, 0xeb, 0x64, 0x1b, 0x53, 0xe2, 0xb4, 0x5d, 0x35, 0x7e, + 0x1d, 0x2e, 0xb6, 0x8a, 0x21, 0xdb, 0x15, 0xb9, 0x5b, 0xe7, 0xed, 0x12, 0x45, 0x58, 0x32, 0x42, + 0x2f, 0x40, 0xe1, 0xa6, 0xb5, 0x4f, 0x76, 0x3c, 0xd7, 0x09, 0x44, 0xe4, 0xd2, 0x90, 0xe1, 0xf2, + 0xd7, 0x25, 0x39, 0xc1, 0x97, 0xa9, 0x77, 0x55, 0x88, 0x43, 0x76, 0x68, 0x1f, 0xf2, 0x0e, 0xb9, + 0x89, 0x49, 0xc3, 0xae, 0xa6, 0x13, 0x9e, 0xbe, 0x21, 0xa8, 0x09, 0xce, 0x4c, 0xef, 0xc9, 0x32, + 0xac, 0x78, 0xd1, 0xb1, 0xbc, 0xe1, 0x6e, 0x8b, 0x8d, 0x6a, 0xc8, 0xb1, 0x54, 0x27, 0x53, 0x3e, + 0x96, 0x97, 0xdc, 0x6d, 0x4c, 0x89, 0xd3, 0x35, 0x52, 0x55, 0x61, 0x33, 0x62, 0x9b, 0xda, 0x48, + 0x37, 0x5c, 0x88, 0xaf, 0x91, 0xb0, 0x14, 0x6b, 0x1c, 0x69, 0xdf, 0xd6, 0x85, 0xcf, 0x50, 0x6c, + 0x54, 0x43, 0xf6, 0x6d, 0xd4, 0x03, 0xc9, 0xfb, 0x56, 0x96, 0x61, 0xc5, 0x8b, 0xf2, 0xb5, 0x85, + 0x03, 0x2e, 0x9d, 0xad, 0x2a, 0xea, 0xce, 0xe3, 0x7c, 0x65, 0x19, 0x56, 0xbc, 0xcc, 0x2f, 0x8f, + 0xc2, 0x84, 0x9e, 0xf6, 0xab, 0x0f, 0x1b, 0x41, 0xd9, 0xc5, 0x99, 0x41, 0xec, 0x62, 0x7a, 0x10, + 0xd2, 0xae, 0x1a, 0xa4, 0x2f, 0x64, 0x35, 0x35, 0xb3, 0x30, 0x3c, 0x08, 0x69, 0x85, 0x3e, 0x8e, + 0x30, 0x1d, 0x20, 0xfa, 0x80, 0x1a, 0x57, 0xdc, 0xfc, 0xc8, 0x45, 0x8d, 0xab, 0x88, 0x41, 0xf1, + 0x08, 0x40, 0x98, 0xfe, 0x4a, 0x5c, 0x41, 0x29, 0xab, 0x4d, 0x4b, 0xcb, 0xa5, 0x61, 0xa1, 0x87, + 0x60, 0x94, 0x2a, 0x68, 0x52, 0x13, 0x0f, 0x66, 0xd5, 0x69, 0xf3, 0x02, 0x2b, 0xc5, 0x02, 0x8a, + 0x9e, 0xa0, 0xb6, 0x54, 0xa8, 0x56, 0xc5, 0x3b, 0xd8, 0xb9, 0xd0, 0x96, 0x0a, 0x61, 0x38, 0x82, + 0x49, 0x45, 0x27, 0x54, 0x0b, 0xb2, 0x19, 0xac, 0x89, 0xce, 0x54, 0x23, 0xe6, 0x30, 0xe6, 0xfd, + 0x88, 0x69, 0x4d, 0x36, 0xf3, 0x72, 0x9a, 0xf7, 0x23, 0x06, 0xc7, 0x5d, 0x35, 0x68, 0x63, 0xc4, + 0xed, 0xd9, 0x38, 0x0f, 0xb2, 0xec, 0x71, 0xef, 0xf5, 0xaa, 0x7e, 0x22, 0x98, 0x60, 0x43, 0xff, + 0xbe, 0xf4, 0x52, 0xd8, 0xf5, 0x7f, 0x24, 0x18, 0xce, 0x78, 0xff, 0x08, 0x4c, 0x45, 0xf7, 0xca, + 0xd4, 0xdd, 0xe4, 0x7f, 0x95, 0x85, 0x53, 0x1b, 0x75, 0xdb, 0xb9, 0x15, 0xf3, 0x2f, 0x27, 0xa5, + 0x96, 0x35, 0x06, 0x4d, 0x2d, 0x1b, 0xbe, 0xbc, 0x11, 0xb9, 0x7b, 0x93, 0x5f, 0xde, 0xc8, 0xc4, + 0xbe, 0x51, 0x5c, 0xf4, 0x03, 0x03, 0xee, 0xb7, 0x6a, 0xdc, 0x7a, 0xb5, 0x1a, 0xa2, 0x34, 0x64, + 0x2a, 0x57, 0xb4, 0x3f, 0xa4, 0x2e, 0xea, 0x6e, 0xfc, 0x62, 0xe9, 0x08, 0xae, 0x7c, 0xc4, 0xdf, + 0x26, 0x5a, 0x70, 0xff, 0x51, 0xa8, 0xf8, 0x48, 0xf1, 0x17, 0x2e, 0xc3, 0x5b, 0xef, 0xc8, 0x68, + 0xa0, 0xd9, 0xf2, 0x49, 0x03, 0x0a, 0xdc, 0x7d, 0x8a, 0xc9, 0x0e, 0xdd, 0x2a, 0xac, 0x96, 0x7d, + 0x8d, 0x78, 0xbe, 0xcc, 0x79, 0xa5, 0x1d, 0xf0, 0x4a, 0x9b, 0xab, 0x02, 0x82, 0x35, 0x2c, 0xba, + 0x19, 0xef, 0xd9, 0x4e, 0x4d, 0x0c, 0x93, 0xda, 0x8c, 0x9f, 0xb5, 0x9d, 0x1a, 0x66, 0x10, 0xb5, + 0x5d, 0x67, 0x7b, 0xba, 0x35, 0x5e, 0x37, 0x60, 0x8a, 0x3d, 0x37, 0x0c, 0x8f, 0x1e, 0x8f, 0xab, + 0xd0, 0x12, 0x2e, 0xc6, 0xd9, 0x68, 0x68, 0xc9, 0xed, 0x4e, 0x71, 0x9c, 0x3f, 0x50, 0x8c, 0x46, + 0x9a, 0x7c, 0x40, 0xf8, 0x2b, 0x58, 0x00, 0x4c, 0x66, 0xe0, 0xe3, 0xb4, 0xf2, 0xe7, 0x55, 0x24, + 0x11, 0x1c, 0xd2, 0x33, 0x5f, 0x84, 0x09, 0xfd, 0xdd, 0x00, 0x7a, 0x1c, 0xc6, 0x5b, 0xb6, 0x53, + 0x8f, 0xbe, 0x2f, 0x53, 0x3e, 0xdd, 0xcd, 0x10, 0x84, 0x75, 0x3c, 0x56, 0xcd, 0x0d, 0xab, 0xc5, + 0x5c, 0xc1, 0x9b, 0xae, 0x5e, 0x2d, 0xfc, 0x63, 0xfe, 0x71, 0x16, 0x4e, 0x25, 0xbc, 0x4f, 0x41, + 0xaf, 0x18, 0x30, 0xca, 0x82, 0xe5, 0x65, 0xf0, 0xc8, 0xf3, 0xa9, 0xbf, 0x81, 0x59, 0x64, 0x31, + 0xf9, 0x62, 0x1e, 0xab, 0xed, 0x93, 0x17, 0x62, 0xc1, 0x1c, 0xfd, 0x96, 0x01, 0xe3, 0x96, 0xb6, + 0xd4, 0x78, 0x3c, 0xcd, 0x76, 0xfa, 0xc2, 0x74, 0xad, 0x2c, 0x2d, 0x0e, 0x30, 0x5c, 0x48, 0xba, + 0x2c, 0x0b, 0xef, 0x81, 0x71, 0xad, 0x09, 0x83, 0xac, 0x90, 0x85, 0xa7, 0x61, 0x66, 0xa8, 0x15, + 0xf6, 0x7e, 0x18, 0x34, 0x85, 0x1b, 0x55, 0x58, 0x37, 0xf5, 0x37, 0xc0, 0xaa, 0xc7, 0xc5, 0x23, + 0x60, 0x01, 0x35, 0xb7, 0x61, 0x26, 0x7e, 0xb8, 0x4a, 0xfd, 0xfa, 0xf8, 0x5d, 0x30, 0x60, 0xd2, + 0x35, 0xf3, 0xaf, 0x33, 0x30, 0x26, 0x1e, 0xb9, 0xdd, 0x85, 0x10, 0xda, 0xbd, 0xc8, 0xa5, 0xce, + 0x6a, 0x2a, 0x6f, 0xf3, 0x7a, 0xc6, 0xcf, 0xfa, 0xb1, 0xf8, 0xd9, 0x67, 0xd3, 0x61, 0x77, 0x74, + 0xf0, 0xec, 0xeb, 0x23, 0x30, 0x1d, 0x7b, 0x34, 0x48, 0x4d, 0x95, 0xae, 0x98, 0xb1, 0xab, 0xa9, + 0xbe, 0x4b, 0x54, 0xe1, 0xdd, 0x47, 0x87, 0x8f, 0xf9, 0x91, 0xdc, 0x96, 0x57, 0x52, 0x4b, 0x8b, + 0xfd, 0xb3, 0x34, 0x97, 0x83, 0x86, 0x43, 0xfd, 0x8b, 0x01, 0xf7, 0xf6, 0x7c, 0x5b, 0xca, 0x52, + 0x93, 0x78, 0x51, 0xa8, 0x58, 0x90, 0x29, 0xbf, 0xa0, 0x57, 0x37, 0x2c, 0xf1, 0x6c, 0x12, 0x71, + 0xf6, 0xe8, 0x31, 0x98, 0x60, 0xaa, 0x95, 0xee, 0x29, 0x01, 0x69, 0x09, 0x07, 0x31, 0x73, 0x15, + 0x56, 0xb4, 0x72, 0x1c, 0xc1, 0x32, 0xbf, 0x64, 0xc0, 0x7c, 0xaf, 0x44, 0x15, 0x7d, 0x1c, 0x0c, + 0x7f, 0x21, 0x16, 0xe3, 0x5b, 0xec, 0x8a, 0xf1, 0x8d, 0x1d, 0x0d, 0x65, 0x38, 0xaf, 0x76, 0x2a, + 0xcb, 0xde, 0x21, 0x84, 0xf5, 0x33, 0x06, 0x9c, 0xe9, 0xb1, 0x9a, 0xba, 0x62, 0xbd, 0x8d, 0x63, + 0xc7, 0x7a, 0x67, 0xfa, 0x8d, 0xf5, 0x36, 0xbf, 0x93, 0x85, 0x19, 0x21, 0x4f, 0x68, 0x5f, 0x3d, + 0x11, 0x89, 0x94, 0x7e, 0x5b, 0x2c, 0x52, 0x7a, 0x2e, 0x8e, 0xff, 0xb3, 0x30, 0xe9, 0x37, 0x57, + 0x98, 0xf4, 0x4f, 0x32, 0x70, 0x3a, 0x31, 0x7f, 0x06, 0xfa, 0x54, 0x82, 0x6a, 0xb8, 0x9e, 0x72, + 0xa2, 0x8e, 0x3e, 0x95, 0xc3, 0xb0, 0xb1, 0xc5, 0x9f, 0xd7, 0x63, 0x7a, 0xf9, 0x56, 0xbf, 0x73, + 0x02, 0x29, 0x47, 0x06, 0x0c, 0xef, 0x35, 0x7f, 0x2d, 0x0b, 0x0f, 0xf7, 0x4b, 0xe8, 0x4d, 0xfa, + 0xfc, 0xc3, 0x8f, 0x3c, 0xff, 0xb8, 0x4b, 0x6a, 0xfb, 0x44, 0x5e, 0x82, 0x7c, 0x39, 0xab, 0xd4, + 0x5e, 0xf7, 0xfc, 0xec, 0xeb, 0x36, 0x71, 0x8c, 0x9a, 0x76, 0x32, 0xab, 0x66, 0xb8, 0x15, 0x8e, + 0x55, 0x78, 0xf1, 0xed, 0x4e, 0x71, 0x56, 0x64, 0xda, 0xab, 0x90, 0x40, 0x14, 0x62, 0x59, 0x09, + 0x3d, 0x0c, 0x79, 0x8f, 0x43, 0x65, 0xc0, 0xbb, 0xb8, 0x92, 0xe5, 0x65, 0x58, 0x41, 0xd1, 0x27, + 0x34, 0x5b, 0x78, 0xe4, 0xa4, 0x92, 0x15, 0x1c, 0x75, 0xd3, 0xfc, 0x3c, 0xe4, 0x7d, 0x99, 0x1f, + 0x93, 0x5f, 0x07, 0x3c, 0xda, 0xe7, 0x3b, 0x0a, 0x7a, 0x74, 0x92, 0xc9, 0x32, 0x79, 0xfb, 0x54, + 0x2a, 0x4d, 0x45, 0x12, 0x99, 0xea, 0xd4, 0xc2, 0x7d, 0x8c, 0x90, 0x70, 0x62, 0xf9, 0xae, 0x01, + 0xe3, 0x62, 0xb4, 0xee, 0xc2, 0xd3, 0x8e, 0x1b, 0xd1, 0xa7, 0x1d, 0xe7, 0x53, 0xd9, 0x3b, 0x7a, + 0xbc, 0xeb, 0xb8, 0x01, 0x13, 0x7a, 0x0a, 0x25, 0xf4, 0x9c, 0xb6, 0xf7, 0x19, 0xc3, 0x24, 0x25, + 0x91, 0xbb, 0x63, 0xb8, 0x2f, 0x9a, 0x5f, 0xcc, 0xab, 0x5e, 0x64, 0x7e, 0x08, 0x7d, 0x0e, 0x1a, + 0x47, 0xce, 0x41, 0x7d, 0x0a, 0x64, 0xd2, 0x9f, 0x02, 0x57, 0x20, 0x2f, 0x37, 0x28, 0xa1, 0xc6, + 0x1f, 0xd4, 0xa3, 0xec, 0xa8, 0x2d, 0x40, 0x89, 0x69, 0x13, 0x97, 0x1d, 0xb5, 0xd4, 0x18, 0xaa, + 0x8d, 0x53, 0x91, 0x41, 0x2f, 0xc0, 0xf8, 0x4d, 0xd7, 0xdb, 0x6b, 0xb8, 0x16, 0xcb, 0x7c, 0x0b, + 0x69, 0x5c, 0xec, 0x28, 0x87, 0x17, 0x8f, 0x38, 0xbe, 0x1e, 0xd2, 0xc7, 0x3a, 0x33, 0x54, 0x82, + 0xe9, 0xa6, 0xed, 0x60, 0x62, 0xd5, 0xd4, 0x0b, 0x8e, 0x11, 0x9e, 0x9a, 0x53, 0x1a, 0xb9, 0xeb, + 0x51, 0x30, 0x8e, 0xe3, 0xa3, 0x8f, 0x41, 0xde, 0x17, 0x09, 0x89, 0xd2, 0xb9, 0x82, 0x53, 0x67, + 0x46, 0x4e, 0x34, 0xec, 0x3b, 0x59, 0x82, 0x15, 0x43, 0xb4, 0x06, 0x73, 0x9e, 0x48, 0xf9, 0x11, + 0xf9, 0x6e, 0x06, 0x5f, 0x9f, 0x2c, 0x03, 0x24, 0x4e, 0x80, 0xe3, 0xc4, 0x5a, 0xd4, 0x8a, 0x61, + 0xb9, 0xc0, 0xf8, 0x9d, 0x80, 0xe6, 0x46, 0x67, 0x13, 0xbe, 0x86, 0x05, 0xf4, 0xa8, 0x17, 0x41, + 0xf9, 0x21, 0x5e, 0x04, 0x55, 0xe0, 0x74, 0x1c, 0xc4, 0x12, 0x93, 0xb0, 0x5c, 0x28, 0x9a, 0xf6, + 0xd8, 0x4c, 0x42, 0xc2, 0xc9, 0x75, 0xd1, 0x75, 0x28, 0x78, 0x84, 0x9d, 0x2f, 0x4a, 0xf2, 0xd2, + 0x7f, 0xe0, 0xf0, 0x26, 0x2c, 0x09, 0xe0, 0x90, 0x16, 0x1d, 0x77, 0x2b, 0x9a, 0x9d, 0xf2, 0x4a, + 0x8a, 0x5f, 0xfe, 0x12, 0x63, 0xdf, 0x23, 0x61, 0x90, 0xf9, 0xc6, 0x14, 0x4c, 0x46, 0x7c, 0x0b, + 0xe8, 0x41, 0xc8, 0xb1, 0x4c, 0x2d, 0x6c, 0x7b, 0xc8, 0x87, 0x5b, 0x18, 0xef, 0x1c, 0x0e, 0x43, + 0x9f, 0x35, 0x60, 0xba, 0x15, 0xf1, 0xc2, 0xca, 0x9d, 0x73, 0xc8, 0x7b, 0xbe, 0xa8, 0x6b, 0x57, + 0xcb, 0xeb, 0x1c, 0x65, 0x86, 0xe3, 0xdc, 0xe9, 0x02, 0x14, 0x31, 0x82, 0x0d, 0xe2, 0x31, 0x6c, + 0x61, 0xe3, 0x28, 0x12, 0xcb, 0x51, 0x30, 0x8e, 0xe3, 0xd3, 0x11, 0x66, 0xad, 0x1b, 0xe6, 0x93, + 0x40, 0x25, 0x49, 0x00, 0x87, 0xb4, 0xd0, 0xd3, 0x30, 0x25, 0x92, 0x12, 0x6e, 0xba, 0xb5, 0x8b, + 0x96, 0xbf, 0x2b, 0x8c, 0x7b, 0x75, 0x18, 0x59, 0x8e, 0x40, 0x71, 0x0c, 0x9b, 0xb5, 0x2d, 0xcc, + 0xfc, 0xc8, 0x08, 0x8c, 0x46, 0xd3, 0x5e, 0x2f, 0x47, 0xc1, 0x38, 0x8e, 0x8f, 0xde, 0xa1, 0xed, + 0xfb, 0xfc, 0x9e, 0x4e, 0xed, 0x06, 0x09, 0x7b, 0x7f, 0x09, 0xa6, 0xdb, 0xec, 0x2c, 0x54, 0x93, + 0x40, 0xb1, 0x1e, 0x15, 0xc3, 0xab, 0x51, 0x30, 0x8e, 0xe3, 0xa3, 0x27, 0x61, 0xd2, 0xa3, 0xbb, + 0x9b, 0x22, 0xc0, 0x2f, 0xef, 0xd4, 0xdd, 0x0c, 0xd6, 0x81, 0x38, 0x8a, 0x8b, 0x9e, 0x81, 0xd9, + 0x30, 0x87, 0x97, 0x24, 0xc0, 0x6f, 0xf3, 0x54, 0x7a, 0x9a, 0x52, 0x1c, 0x01, 0x77, 0xd7, 0x41, + 0xbf, 0x04, 0x33, 0x5a, 0x4f, 0xac, 0x3a, 0x35, 0x72, 0x4b, 0xe4, 0x59, 0x62, 0x5f, 0x28, 0x58, + 0x8e, 0xc1, 0x70, 0x17, 0x36, 0x7a, 0x2f, 0x4c, 0x55, 0xdd, 0x46, 0x83, 0xed, 0x71, 0x3c, 0xe5, + 0x32, 0x4f, 0xa8, 0xc4, 0x53, 0x4f, 0x45, 0x20, 0x38, 0x86, 0x89, 0x2e, 0x01, 0x72, 0xb7, 0x7d, + 0xe2, 0xed, 0x93, 0xda, 0x33, 0xfc, 0x23, 0xa3, 0x54, 0xc5, 0x4f, 0x46, 0x23, 0x94, 0x2f, 0x77, + 0x61, 0xe0, 0x84, 0x5a, 0x2c, 0xbb, 0x8d, 0xf6, 0xb0, 0x6a, 0x2a, 0x8d, 0xcf, 0xe3, 0xc4, 0x4f, + 0xee, 0x77, 0x7c, 0x55, 0xe5, 0xc1, 0x28, 0x0f, 0x18, 0x4f, 0x27, 0xb3, 0x92, 0x9e, 0x7d, 0x35, + 0xd4, 0x11, 0xbc, 0x14, 0x0b, 0x4e, 0xe8, 0xe3, 0x50, 0xd8, 0x96, 0xa9, 0xb8, 0xe7, 0x67, 0xd2, + 0xd0, 0x8b, 0xb1, 0xac, 0xf2, 0xe1, 0xc9, 0x54, 0x01, 0x70, 0xc8, 0x12, 0x3d, 0x04, 0xe3, 0x17, + 0x37, 0x4b, 0x6a, 0x16, 0xce, 0xb2, 0xd1, 0x1f, 0xa1, 0x55, 0xb0, 0x0e, 0xa0, 0x2b, 0x4c, 0xd9, + 0x4b, 0x88, 0x0d, 0x71, 0xa8, 0x6f, 0xbb, 0xcd, 0x1f, 0x8a, 0xcd, 0xae, 0x23, 0x71, 0x65, 0xfe, + 0x54, 0x0c, 0x5b, 0x94, 0x63, 0x85, 0x81, 0x9e, 0x87, 0x71, 0xa1, 0x2f, 0xd8, 0xde, 0x34, 0x77, + 0xbc, 0x47, 0x7b, 0x38, 0x24, 0x81, 0x75, 0x7a, 0xec, 0x96, 0x89, 0x65, 0x28, 0x26, 0x17, 0xda, + 0x8d, 0xc6, 0xfc, 0x69, 0xb6, 0x6f, 0x86, 0xb7, 0x4c, 0x21, 0x08, 0xeb, 0x78, 0xe8, 0x51, 0x19, + 0x39, 0xf1, 0x96, 0xc8, 0xb5, 0x9b, 0x8a, 0x9c, 0x50, 0x56, 0x6e, 0x8f, 0x80, 0xe2, 0x33, 0x77, + 0x08, 0x59, 0xd8, 0x86, 0x05, 0x69, 0x62, 0x75, 0x2f, 0x92, 0xf9, 0xf9, 0x88, 0x97, 0x60, 0xe1, + 0x7a, 0x4f, 0x4c, 0x7c, 0x04, 0x15, 0xb4, 0x0d, 0x59, 0xab, 0xb1, 0x3d, 0x7f, 0x6f, 0x1a, 0xb6, + 0xa2, 0xfa, 0x68, 0x30, 0x0f, 0x02, 0x2a, 0xad, 0x95, 0x31, 0x25, 0x6e, 0xbe, 0x9c, 0x51, 0x5e, + 0x79, 0x95, 0x71, 0xf2, 0x45, 0x7d, 0x56, 0x1b, 0x69, 0x7c, 0x14, 0xb3, 0x2b, 0x5f, 0x3d, 0x57, + 0x48, 0x89, 0x73, 0xba, 0xa5, 0xd6, 0x71, 0x2a, 0xe9, 0x44, 0xa2, 0xd9, 0x34, 0xf9, 0x69, 0x2e, + 0xba, 0x8a, 0xcd, 0xc3, 0x31, 0xe5, 0x84, 0x8a, 0x85, 0x02, 0x78, 0x90, 0xb3, 0xfd, 0xc0, 0x76, + 0x53, 0x7c, 0x60, 0x16, 0x4b, 0x43, 0xc9, 0x02, 0x67, 0x19, 0x00, 0x73, 0x56, 0x94, 0xa7, 0x53, + 0xb7, 0x9d, 0x5b, 0xa2, 0xf9, 0x57, 0x52, 0xbf, 0xe3, 0xe7, 0x3c, 0x19, 0x00, 0x73, 0x56, 0xe8, + 0x06, 0x9f, 0x69, 0xe9, 0x7c, 0x00, 0x35, 0xfe, 0x5d, 0xe3, 0xe8, 0x8c, 0xa3, 0xbc, 0xfc, 0xa6, + 0x2d, 0x6c, 0x98, 0x21, 0x79, 0x55, 0xd6, 0x57, 0x93, 0x78, 0x55, 0xd6, 0x57, 0x31, 0x65, 0x82, + 0x5e, 0x35, 0x00, 0x2c, 0xf5, 0x81, 0xdf, 0x74, 0x3e, 0xee, 0xd0, 0xeb, 0x83, 0xc1, 0x3c, 0xd6, + 0x2d, 0x84, 0x62, 0x8d, 0x33, 0x7a, 0x01, 0xc6, 0x2c, 0xfe, 0x69, 0x1a, 0x11, 0x46, 0x98, 0xce, + 0xf7, 0x96, 0x62, 0x12, 0xb0, 0xf8, 0x49, 0x01, 0xc2, 0x92, 0x21, 0xe5, 0x1d, 0x78, 0x16, 0xd9, + 0xb1, 0xf7, 0x44, 0x3c, 0x61, 0x65, 0xe8, 0x0c, 0xd3, 0x94, 0x58, 0x12, 0x6f, 0x01, 0xc2, 0x92, + 0x21, 0xff, 0xa6, 0xa6, 0xe5, 0x58, 0xea, 0x71, 0x48, 0x3a, 0x4f, 0x88, 0xf4, 0xe7, 0x26, 0xda, + 0x37, 0x35, 0x75, 0x46, 0x38, 0xca, 0xd7, 0xfc, 0x51, 0x16, 0x80, 0xfd, 0xe4, 0xef, 0x86, 0x9b, + 0x2c, 0xd7, 0xdc, 0xae, 0x5b, 0x13, 0x4b, 0x3b, 0xc5, 0xe7, 0xbf, 0x20, 0x12, 0xcb, 0xed, 0xba, + 0x35, 0x2c, 0x98, 0xa0, 0x3a, 0x8c, 0xb4, 0xac, 0x60, 0x37, 0xfd, 0xb7, 0xc6, 0x79, 0xfe, 0x72, + 0x27, 0xd8, 0xc5, 0x8c, 0x01, 0x7a, 0xc9, 0x80, 0x31, 0xfe, 0xda, 0x58, 0xba, 0x9a, 0x87, 0xbe, + 0x4f, 0x95, 0x7d, 0xb6, 0xc8, 0x9f, 0x34, 0x8b, 0x60, 0x05, 0xa5, 0x1a, 0x45, 0x29, 0x96, 0x6c, + 0x17, 0x5e, 0x31, 0x60, 0x42, 0x47, 0x4d, 0x08, 0x33, 0xf8, 0xb0, 0x1e, 0x66, 0x90, 0x66, 0x7f, + 0xe8, 0x11, 0x0b, 0xff, 0x6e, 0x80, 0xf6, 0x25, 0xd2, 0x30, 0xc8, 0xd0, 0xe8, 0x3b, 0xc8, 0x30, + 0x33, 0x60, 0x90, 0x61, 0x76, 0xa0, 0x20, 0xc3, 0x91, 0xc1, 0x83, 0x0c, 0x73, 0xbd, 0x83, 0x0c, + 0xcd, 0xd7, 0x0c, 0x98, 0xed, 0xda, 0x0f, 0xe3, 0x5f, 0x7c, 0x37, 0xfa, 0xfc, 0xe2, 0xfb, 0x0a, + 0xcc, 0x88, 0x5c, 0xc8, 0x95, 0x56, 0xc3, 0x4e, 0x7c, 0x07, 0xbe, 0x15, 0x83, 0xe3, 0xae, 0x1a, + 0xe6, 0x9f, 0x1b, 0x30, 0xae, 0x3d, 0x5b, 0xa3, 0xed, 0x60, 0xcf, 0xfb, 0x84, 0x18, 0x61, 0x1a, + 0x68, 0xe6, 0xda, 0xe7, 0x30, 0x7e, 0xcb, 0x54, 0xd7, 0xf2, 0x6e, 0x86, 0xb7, 0x4c, 0xb4, 0x14, + 0x0b, 0x28, 0xcf, 0xa8, 0x48, 0xf8, 0xd7, 0xfc, 0xb3, 0x7a, 0x46, 0x45, 0xd2, 0xc2, 0x0c, 0xc2, + 0xd8, 0x51, 0x3b, 0x52, 0xc4, 0x9f, 0x6a, 0x59, 0xa7, 0x2d, 0x2f, 0xc0, 0x1c, 0x86, 0xce, 0x42, + 0x96, 0x38, 0x35, 0x71, 0xe8, 0x55, 0x5f, 0x7a, 0x3a, 0xef, 0xd4, 0x30, 0x2d, 0x37, 0x2f, 0xc3, + 0x44, 0x85, 0x54, 0x3d, 0x12, 0x3c, 0x4b, 0x0e, 0xfa, 0xfe, 0x74, 0x14, 0x9d, 0xed, 0xb1, 0x4f, + 0x47, 0xd1, 0xea, 0xb4, 0xdc, 0xfc, 0x43, 0x03, 0x62, 0xa9, 0xd1, 0x35, 0x8f, 0xb3, 0xd1, 0xcb, + 0xe3, 0x1c, 0xf1, 0x8d, 0x66, 0x8e, 0xf4, 0x8d, 0x5e, 0x02, 0xd4, 0xa4, 0x4b, 0x21, 0xf2, 0x21, + 0x00, 0xe1, 0x6f, 0x08, 0x1f, 0xc9, 0x76, 0x61, 0xe0, 0x84, 0x5a, 0xe6, 0x1f, 0x70, 0x61, 0xf5, + 0x64, 0xe9, 0x77, 0xee, 0x80, 0x36, 0xe4, 0x18, 0x29, 0xe1, 0x74, 0xd9, 0x1c, 0x6e, 0x71, 0x77, + 0xe7, 0x7c, 0x08, 0x07, 0x52, 0x2c, 0x79, 0xc6, 0xcd, 0xfc, 0x0e, 0x97, 0x55, 0xcb, 0xa6, 0xde, + 0x87, 0xac, 0xcd, 0xa8, 0xac, 0x17, 0xd3, 0xda, 0x2b, 0x93, 0x65, 0x44, 0x8b, 0x00, 0x2d, 0xe2, + 0x55, 0x89, 0x13, 0xc8, 0xb0, 0xe8, 0x9c, 0x78, 0x46, 0xa2, 0x4a, 0xb1, 0x86, 0x61, 0xbe, 0x64, + 0xc0, 0x4c, 0x25, 0xb0, 0xab, 0x7b, 0xb6, 0xc3, 0x9f, 0x45, 0xed, 0xd8, 0x75, 0x7a, 0x4a, 0x21, + 0xe2, 0xab, 0x48, 0xdc, 0x0d, 0xa6, 0xb6, 0x62, 0xf9, 0x31, 0x24, 0x09, 0x47, 0x25, 0x98, 0x96, + 0xde, 0x76, 0xe9, 0xbb, 0xe4, 0xcf, 0x39, 0x95, 0xaf, 0x64, 0x25, 0x0a, 0xc6, 0x71, 0x7c, 0xf3, + 0x13, 0x30, 0xae, 0xed, 0xaf, 0x6c, 0x2b, 0xba, 0x65, 0x55, 0x83, 0xf8, 0x12, 0x3e, 0x4f, 0x0b, + 0x31, 0x87, 0x31, 0x17, 0x2b, 0x8f, 0x9b, 0x8d, 0x2d, 0x61, 0x11, 0x2d, 0x2b, 0xa0, 0x94, 0x98, + 0x47, 0xea, 0xe4, 0x96, 0xcc, 0xf0, 0x29, 0x89, 0x61, 0x5a, 0x88, 0x39, 0xcc, 0xbc, 0x06, 0x79, + 0xf9, 0xe8, 0x9e, 0xbd, 0x5c, 0x95, 0xee, 0x3f, 0xfd, 0xe5, 0xaa, 0xeb, 0x05, 0x98, 0x41, 0xe8, + 0x3a, 0xf1, 0x1d, 0xfb, 0xa2, 0xeb, 0x07, 0x32, 0x53, 0x00, 0x77, 0xf2, 0x6f, 0xac, 0xb2, 0x32, + 0xac, 0xa0, 0xe6, 0x2c, 0x4c, 0x2b, 0xef, 0xbd, 0x08, 0x4d, 0xfc, 0x46, 0x16, 0x26, 0x22, 0xdf, + 0xd8, 0xbd, 0xf3, 0x04, 0xea, 0x7f, 0x5d, 0x26, 0x78, 0xe1, 0xb3, 0x03, 0x7a, 0xe1, 0xf5, 0x6b, + 0x8f, 0x91, 0x93, 0xbd, 0xf6, 0xc8, 0xa5, 0x73, 0xed, 0x11, 0xc0, 0x98, 0x2f, 0x54, 0xcf, 0x68, + 0x1a, 0xee, 0x91, 0xd8, 0x88, 0x71, 0xab, 0x53, 0x6a, 0x30, 0xc9, 0xca, 0xfc, 0x6a, 0x0e, 0xa6, + 0xa2, 0xe9, 0x86, 0xfa, 0x18, 0xc9, 0x77, 0x74, 0x8d, 0xe4, 0x80, 0x5e, 0xc8, 0xec, 0xb0, 0x5e, + 0xc8, 0x91, 0x61, 0xbd, 0x90, 0xb9, 0x63, 0x78, 0x21, 0xbb, 0x7d, 0x88, 0xa3, 0x7d, 0xfb, 0x10, + 0x9f, 0x52, 0x21, 0x34, 0x63, 0x91, 0x3b, 0xe7, 0x30, 0x84, 0x06, 0x45, 0x87, 0x61, 0xd9, 0xad, + 0x25, 0x86, 0x22, 0xe5, 0xef, 0xe0, 0x6d, 0xf1, 0x12, 0x23, 0x5e, 0x06, 0xbf, 0xe8, 0x78, 0xcb, + 0x00, 0xd1, 0x2e, 0x8f, 0xc3, 0xb8, 0x98, 0x4f, 0xcc, 0xfa, 0x81, 0xa8, 0xe5, 0x54, 0x09, 0x41, + 0x58, 0xc7, 0x63, 0x9f, 0x81, 0x8c, 0x7e, 0xf7, 0x92, 0x39, 0x75, 0xf5, 0xcf, 0x40, 0xc6, 0xbe, + 0x93, 0x19, 0xc7, 0x37, 0x3f, 0x06, 0xa7, 0x13, 0xcf, 0x58, 0xcc, 0xe9, 0xc4, 0x14, 0x33, 0xa9, + 0x09, 0x04, 0x4d, 0x8c, 0x58, 0x36, 0xda, 0x85, 0xeb, 0x3d, 0x31, 0xf1, 0x11, 0x54, 0xcc, 0xaf, + 0x64, 0x61, 0x2a, 0xfa, 0x0d, 0x21, 0x74, 0x53, 0x79, 0x64, 0x52, 0x71, 0x06, 0x71, 0xb2, 0x5a, + 0x0a, 0x9b, 0x9e, 0xee, 0xd5, 0x9b, 0x6c, 0x7e, 0x6d, 0xab, 0x7c, 0x3a, 0x27, 0xc7, 0x58, 0xf8, + 0x35, 0x05, 0x3b, 0xf6, 0x99, 0xa0, 0xf0, 0x01, 0x83, 0x38, 0x48, 0xa5, 0xce, 0x3d, 0x7c, 0x92, + 0xa0, 0x58, 0x61, 0x8d, 0x2d, 0xd5, 0x2d, 0xfb, 0xc4, 0xb3, 0x77, 0x6c, 0xf5, 0xfd, 0x43, 0xb6, + 0x73, 0x5f, 0x13, 0x65, 0x58, 0x41, 0xcd, 0x97, 0x32, 0x10, 0x7e, 0xed, 0x95, 0x7d, 0x68, 0xc3, + 0xd7, 0x8c, 0x56, 0x31, 0x6c, 0x97, 0x86, 0xfd, 0x9a, 0x4d, 0x48, 0x51, 0x84, 0x37, 0x6a, 0x25, + 0x38, 0xc2, 0xf1, 0xa7, 0xf0, 0x95, 0x57, 0x0b, 0xa6, 0x63, 0xcf, 0x3a, 0x53, 0x8f, 0x21, 0xff, + 0x62, 0x16, 0x0a, 0xea, 0x61, 0x2c, 0x7a, 0x4f, 0xc4, 0x83, 0x50, 0x28, 0xbf, 0x55, 0xcb, 0x29, + 0xbf, 0xeb, 0xd6, 0x6e, 0x77, 0x8a, 0xd3, 0x0a, 0x39, 0xe6, 0x0d, 0x38, 0x0b, 0xd9, 0xb6, 0xd7, + 0x88, 0x1f, 0x11, 0xae, 0xe2, 0x35, 0x4c, 0xcb, 0xd1, 0xad, 0xf8, 0x11, 0x7e, 0x3d, 0xa5, 0xc7, + 0xbc, 0xdc, 0x96, 0xee, 0x7d, 0x74, 0xa7, 0x5a, 0x72, 0xdb, 0xad, 0x1d, 0xc4, 0x73, 0xd0, 0x97, + 0xdd, 0xda, 0x01, 0x66, 0x10, 0xf4, 0x34, 0x4c, 0x05, 0x76, 0x93, 0xb8, 0xed, 0x40, 0xff, 0x96, + 0x66, 0x36, 0xbc, 0x2e, 0xdc, 0x8a, 0x40, 0x71, 0x0c, 0x9b, 0x6a, 0xd9, 0x1b, 0xbe, 0xeb, 0xb0, + 0xc4, 0x72, 0xa3, 0xd1, 0xbb, 0x85, 0x4b, 0x95, 0xcb, 0x1b, 0xcc, 0x93, 0xa1, 0x30, 0x28, 0xb6, + 0xcd, 0x5e, 0xc1, 0x79, 0x44, 0xdc, 0xd6, 0xcf, 0x84, 0x39, 0x12, 0x78, 0x39, 0x56, 0x18, 0xe6, + 0x55, 0x98, 0x8e, 0x35, 0x55, 0x1e, 0xc6, 0x8c, 0xe4, 0xc3, 0x58, 0x7f, 0x09, 0xdf, 0xff, 0xc4, + 0x80, 0xd9, 0xae, 0xc5, 0xdb, 0xef, 0xe3, 0x86, 0xb8, 0x1a, 0xc9, 0x1c, 0x5f, 0x8d, 0x64, 0x07, + 0x53, 0x23, 0xe5, 0xed, 0x6f, 0xbd, 0x71, 0xee, 0x9e, 0x6f, 0xbf, 0x71, 0xee, 0x9e, 0xef, 0xbd, + 0x71, 0xee, 0x9e, 0x97, 0x0e, 0xcf, 0x19, 0xdf, 0x3a, 0x3c, 0x67, 0x7c, 0xfb, 0xf0, 0x9c, 0xf1, + 0xbd, 0xc3, 0x73, 0xc6, 0x3f, 0x1f, 0x9e, 0x33, 0x5e, 0xfb, 0xe1, 0xb9, 0x7b, 0x9e, 0x7b, 0x2a, + 0x9c, 0x5a, 0x4b, 0x72, 0x6a, 0xb1, 0x1f, 0xef, 0x94, 0x13, 0x69, 0xa9, 0xb5, 0x57, 0x5f, 0xa2, + 0x53, 0x6b, 0x49, 0x95, 0xc8, 0xa9, 0xf5, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x19, 0xc7, + 0x30, 0x68, 0x92, 0x00, 0x00, } func (m *ALBStatus) Marshal() (dAtA []byte, err error) { @@ -4672,9 +4803,21 @@ func (m *CanaryStep) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.SetHeaderRouting != nil { + if m.SetMirrorRoute != nil { + { + size, err := m.SetMirrorRoute.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + if m.SetHeaderRoute != nil { { - size, err := m.SetHeaderRouting.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.SetHeaderRoute.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -5849,16 +5992,18 @@ func (m *HeaderRoutingMatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - { - size, err := m.HeaderValue.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if m.HeaderValue != nil { + { + size, err := m.HeaderValue.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) } - i -= size - i = encodeVarintGenerated(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 } - i-- - dAtA[i] = 0x12 i -= len(m.HeaderName) copy(dAtA[i:], m.HeaderName) i = encodeVarintGenerated(dAtA, i, uint64(len(m.HeaderName))) @@ -5867,6 +6012,39 @@ func (m *HeaderRoutingMatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *InfluxdbMetric) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *InfluxdbMetric) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *InfluxdbMetric) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Query) + copy(dAtA[i:], m.Query) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Query))) + i-- + dAtA[i] = 0x12 + i -= len(m.Profile) + copy(dAtA[i:], m.Profile) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Profile))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *IstioDestinationRule) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6214,6 +6392,34 @@ func (m *KayentaThreshold) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *MangedRoutes) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MangedRoutes) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MangedRoutes) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func (m *Measurement) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6469,9 +6675,21 @@ func (m *MetricProvider) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Graphite != nil { + if m.Influxdb != nil { { - size, err := m.Graphite.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Influxdb.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + } + if m.Graphite != nil { + { + size, err := m.Graphite.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -8015,6 +8233,20 @@ func (m *RolloutTrafficRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ManagedRoutes) > 0 { + for iNdEx := len(m.ManagedRoutes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ManagedRoutes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + } + } if m.Traefik != nil { { size, err := m.Traefik.MarshalToSizedBuffer(dAtA[:i]) @@ -8102,6 +8334,82 @@ func (m *RolloutTrafficRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RouteMatch) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RouteMatch) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RouteMatch) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Headers) > 0 { + keysForHeaders := make([]string, 0, len(m.Headers)) + for k := range m.Headers { + keysForHeaders = append(keysForHeaders, string(k)) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForHeaders) + for iNdEx := len(keysForHeaders) - 1; iNdEx >= 0; iNdEx-- { + v := m.Headers[string(keysForHeaders[iNdEx])] + baseI := i + { + size, err := (&v).MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + i -= len(keysForHeaders[iNdEx]) + copy(dAtA[i:], keysForHeaders[iNdEx]) + i = encodeVarintGenerated(dAtA, i, uint64(len(keysForHeaders[iNdEx]))) + i-- + dAtA[i] = 0xa + i = encodeVarintGenerated(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x1a + } + } + if m.Path != nil { + { + size, err := m.Path.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if m.Method != nil { + { + size, err := m.Method.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *RunSummary) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -8293,7 +8601,7 @@ func (m *SetCanaryScale) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *SetHeaderRouting) Marshal() (dAtA []byte, err error) { +func (m *SetHeaderRoute) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -8303,12 +8611,12 @@ func (m *SetHeaderRouting) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *SetHeaderRouting) MarshalTo(dAtA []byte) (int, error) { +func (m *SetHeaderRoute) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *SetHeaderRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *SetHeaderRoute) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -8324,9 +8632,61 @@ func (m *SetHeaderRouting) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 + } + } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *SetMirrorRoute) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SetMirrorRoute) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SetMirrorRoute) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Percentage != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Percentage)) + i-- + dAtA[i] = 0x20 + } + if len(m.Match) > 0 { + for iNdEx := len(m.Match) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Match[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } } + i -= len(m.Name) + copy(dAtA[i:], m.Name) + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -9423,8 +9783,12 @@ func (m *CanaryStep) Size() (n int) { l = m.SetCanaryScale.Size() n += 1 + l + sovGenerated(uint64(l)) } - if m.SetHeaderRouting != nil { - l = m.SetHeaderRouting.Size() + if m.SetHeaderRoute != nil { + l = m.SetHeaderRoute.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.SetMirrorRoute != nil { + l = m.SetMirrorRoute.Size() n += 1 + l + sovGenerated(uint64(l)) } return n @@ -9843,7 +10207,22 @@ func (m *HeaderRoutingMatch) Size() (n int) { _ = l l = len(m.HeaderName) n += 1 + l + sovGenerated(uint64(l)) - l = m.HeaderValue.Size() + if m.HeaderValue != nil { + l = m.HeaderValue.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *InfluxdbMetric) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Profile) + n += 1 + l + sovGenerated(uint64(l)) + l = len(m.Query) n += 1 + l + sovGenerated(uint64(l)) return n } @@ -9977,6 +10356,17 @@ func (m *KayentaThreshold) Size() (n int) { return n } +func (m *MangedRoutes) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + return n +} + func (m *Measurement) Size() (n int) { if m == nil { return 0 @@ -10103,6 +10493,10 @@ func (m *MetricProvider) Size() (n int) { l = m.Graphite.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Influxdb != nil { + l = m.Influxdb.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -10642,6 +11036,38 @@ func (m *RolloutTrafficRouting) Size() (n int) { l = m.Traefik.Size() n += 1 + l + sovGenerated(uint64(l)) } + if len(m.ManagedRoutes) > 0 { + for _, e := range m.ManagedRoutes { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *RouteMatch) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Method != nil { + l = m.Method.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.Path != nil { + l = m.Path.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if len(m.Headers) > 0 { + for k, v := range m.Headers { + _ = k + _ = v + l = v.Size() + mapEntrySize := 1 + len(k) + sovGenerated(uint64(len(k))) + 1 + l + sovGenerated(uint64(l)) + n += mapEntrySize + 1 + sovGenerated(uint64(mapEntrySize)) + } + } return n } @@ -10719,18 +11145,40 @@ func (m *SetCanaryScale) Size() (n int) { return n } -func (m *SetHeaderRouting) Size() (n int) { +func (m *SetHeaderRoute) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Match) > 0 { + for _, e := range m.Match { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *SetMirrorRoute) Size() (n int) { if m == nil { return 0 } var l int _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) if len(m.Match) > 0 { for _, e := range m.Match { l = e.Size() n += 1 + l + sovGenerated(uint64(l)) } } + if m.Percentage != nil { + n += 1 + sovGenerated(uint64(*m.Percentage)) + } return n } @@ -11311,7 +11759,8 @@ func (this *CanaryStep) String() string { `Experiment:` + strings.Replace(this.Experiment.String(), "RolloutExperimentStep", "RolloutExperimentStep", 1) + `,`, `Analysis:` + strings.Replace(this.Analysis.String(), "RolloutAnalysis", "RolloutAnalysis", 1) + `,`, `SetCanaryScale:` + strings.Replace(this.SetCanaryScale.String(), "SetCanaryScale", "SetCanaryScale", 1) + `,`, - `SetHeaderRouting:` + strings.Replace(this.SetHeaderRouting.String(), "SetHeaderRouting", "SetHeaderRouting", 1) + `,`, + `SetHeaderRoute:` + strings.Replace(this.SetHeaderRoute.String(), "SetHeaderRoute", "SetHeaderRoute", 1) + `,`, + `SetMirrorRoute:` + strings.Replace(this.SetMirrorRoute.String(), "SetMirrorRoute", "SetMirrorRoute", 1) + `,`, `}`, }, "") return s @@ -11634,7 +12083,18 @@ func (this *HeaderRoutingMatch) String() string { } s := strings.Join([]string{`&HeaderRoutingMatch{`, `HeaderName:` + fmt.Sprintf("%v", this.HeaderName) + `,`, - `HeaderValue:` + strings.Replace(strings.Replace(this.HeaderValue.String(), "StringMatch", "StringMatch", 1), `&`, ``, 1) + `,`, + `HeaderValue:` + strings.Replace(this.HeaderValue.String(), "StringMatch", "StringMatch", 1) + `,`, + `}`, + }, "") + return s +} +func (this *InfluxdbMetric) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&InfluxdbMetric{`, + `Profile:` + fmt.Sprintf("%v", this.Profile) + `,`, + `Query:` + fmt.Sprintf("%v", this.Query) + `,`, `}`, }, "") return s @@ -11741,6 +12201,16 @@ func (this *KayentaThreshold) String() string { }, "") return s } +func (this *MangedRoutes) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MangedRoutes{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `}`, + }, "") + return s +} func (this *Measurement) String() string { if this == nil { return "nil" @@ -11811,6 +12281,7 @@ func (this *MetricProvider) String() string { `Job:` + strings.Replace(this.Job.String(), "JobMetric", "JobMetric", 1) + `,`, `CloudWatch:` + strings.Replace(this.CloudWatch.String(), "CloudWatchMetric", "CloudWatchMetric", 1) + `,`, `Graphite:` + strings.Replace(this.Graphite.String(), "GraphiteMetric", "GraphiteMetric", 1) + `,`, + `Influxdb:` + strings.Replace(this.Influxdb.String(), "InfluxdbMetric", "InfluxdbMetric", 1) + `,`, `}`, }, "") return s @@ -12235,6 +12706,11 @@ func (this *RolloutTrafficRouting) String() string { if this == nil { return "nil" } + repeatedStringForManagedRoutes := "[]MangedRoutes{" + for _, f := range this.ManagedRoutes { + repeatedStringForManagedRoutes += strings.Replace(strings.Replace(f.String(), "MangedRoutes", "MangedRoutes", 1), `&`, ``, 1) + "," + } + repeatedStringForManagedRoutes += "}" s := strings.Join([]string{`&RolloutTrafficRouting{`, `Istio:` + strings.Replace(this.Istio.String(), "IstioTrafficRouting", "IstioTrafficRouting", 1) + `,`, `Nginx:` + strings.Replace(this.Nginx.String(), "NginxTrafficRouting", "NginxTrafficRouting", 1) + `,`, @@ -12243,6 +12719,29 @@ func (this *RolloutTrafficRouting) String() string { `Ambassador:` + strings.Replace(this.Ambassador.String(), "AmbassadorTrafficRouting", "AmbassadorTrafficRouting", 1) + `,`, `AppMesh:` + strings.Replace(this.AppMesh.String(), "AppMeshTrafficRouting", "AppMeshTrafficRouting", 1) + `,`, `Traefik:` + strings.Replace(this.Traefik.String(), "TraefikTrafficRouting", "TraefikTrafficRouting", 1) + `,`, + `ManagedRoutes:` + repeatedStringForManagedRoutes + `,`, + `}`, + }, "") + return s +} +func (this *RouteMatch) String() string { + if this == nil { + return "nil" + } + keysForHeaders := make([]string, 0, len(this.Headers)) + for k := range this.Headers { + keysForHeaders = append(keysForHeaders, k) + } + github_com_gogo_protobuf_sortkeys.Strings(keysForHeaders) + mapStringForHeaders := "map[string]StringMatch{" + for _, k := range keysForHeaders { + mapStringForHeaders += fmt.Sprintf("%v: %v,", k, this.Headers[k]) + } + mapStringForHeaders += "}" + s := strings.Join([]string{`&RouteMatch{`, + `Method:` + strings.Replace(this.Method.String(), "StringMatch", "StringMatch", 1) + `,`, + `Path:` + strings.Replace(this.Path.String(), "StringMatch", "StringMatch", 1) + `,`, + `Headers:` + mapStringForHeaders + `,`, `}`, }, "") return s @@ -12309,7 +12808,7 @@ func (this *SetCanaryScale) String() string { }, "") return s } -func (this *SetHeaderRouting) String() string { +func (this *SetHeaderRoute) String() string { if this == nil { return "nil" } @@ -12318,8 +12817,26 @@ func (this *SetHeaderRouting) String() string { repeatedStringForMatch += strings.Replace(strings.Replace(f.String(), "HeaderRoutingMatch", "HeaderRoutingMatch", 1), `&`, ``, 1) + "," } repeatedStringForMatch += "}" - s := strings.Join([]string{`&SetHeaderRouting{`, + s := strings.Join([]string{`&SetHeaderRoute{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `Match:` + repeatedStringForMatch + `,`, + `}`, + }, "") + return s +} +func (this *SetMirrorRoute) String() string { + if this == nil { + return "nil" + } + repeatedStringForMatch := "[]RouteMatch{" + for _, f := range this.Match { + repeatedStringForMatch += strings.Replace(strings.Replace(f.String(), "RouteMatch", "RouteMatch", 1), `&`, ``, 1) + "," + } + repeatedStringForMatch += "}" + s := strings.Join([]string{`&SetMirrorRoute{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Match:` + repeatedStringForMatch + `,`, + `Percentage:` + valueToStringGenerated(this.Percentage) + `,`, `}`, }, "") return s @@ -16339,7 +16856,7 @@ func (m *CanaryStep) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SetHeaderRouting", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SetHeaderRoute", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -16366,29 +16883,65 @@ func (m *CanaryStep) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.SetHeaderRouting == nil { - m.SetHeaderRouting = &SetHeaderRouting{} + if m.SetHeaderRoute == nil { + m.SetHeaderRoute = &SetHeaderRoute{} } - if err := m.SetHeaderRouting.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.SetHeaderRoute.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SetMirrorRoute", wireType) } - iNdEx += skippy - } - } - + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.SetMirrorRoute == nil { + m.SetMirrorRoute = &SetMirrorRoute{} + } + if err := m.SetMirrorRoute.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { return io.ErrUnexpectedEOF } @@ -19831,6 +20384,9 @@ func (m *HeaderRoutingMatch) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } + if m.HeaderValue == nil { + m.HeaderValue = &StringMatch{} + } if err := m.HeaderValue.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -19856,6 +20412,120 @@ func (m *HeaderRoutingMatch) Unmarshal(dAtA []byte) error { } return nil } +func (m *InfluxdbMetric) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: InfluxdbMetric: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: InfluxdbMetric: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Profile", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Profile = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Query", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Query = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *IstioDestinationRule) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -20967,6 +21637,88 @@ func (m *KayentaThreshold) Unmarshal(dAtA []byte) error { } return nil } +func (m *MangedRoutes) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MangedRoutes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MangedRoutes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Measurement) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -22189,25 +22941,61 @@ func (m *MetricProvider) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Influxdb", wireType) } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Influxdb == nil { + m.Influxdb = &InfluxdbMetric{} + } + if err := m.Influxdb.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } return nil } func (m *MetricResult) Unmarshal(dAtA []byte) error { @@ -27137,6 +27925,291 @@ func (m *RolloutTrafficRouting) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ManagedRoutes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ManagedRoutes = append(m.ManagedRoutes, MangedRoutes{}) + if err := m.ManagedRoutes[len(m.ManagedRoutes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RouteMatch) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RouteMatch: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RouteMatch: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Method", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Method == nil { + m.Method = &StringMatch{} + } + if err := m.Method.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Path == nil { + m.Path = &StringMatch{} + } + if err := m.Path.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Headers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Headers == nil { + m.Headers = make(map[string]StringMatch) + } + var mapkey string + mapvalue := &StringMatch{} + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthGenerated + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthGenerated + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthGenerated + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthGenerated + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &StringMatch{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Headers[mapkey] = *mapvalue + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -27838,7 +28911,7 @@ func (m *SetCanaryScale) Unmarshal(dAtA []byte) error { } return nil } -func (m *SetHeaderRouting) Unmarshal(dAtA []byte) error { +func (m *SetHeaderRoute) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -27861,13 +28934,45 @@ func (m *SetHeaderRouting) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SetHeaderRouting: wiretype end group for non-group") + return fmt.Errorf("proto: SetHeaderRoute: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SetHeaderRouting: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SetHeaderRoute: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Match", wireType) } @@ -27922,6 +29027,142 @@ func (m *SetHeaderRouting) Unmarshal(dAtA []byte) error { } return nil } +func (m *SetMirrorRoute) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SetMirrorRoute: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SetMirrorRoute: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Match", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Match = append(m.Match, RouteMatch{}) + if err := m.Match[len(m.Match)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentage", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Percentage = &v + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *StickinessConfig) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index d035b8aecc..2bdc40c2ec 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -29,7 +29,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; import "k8s.io/apimachinery/pkg/util/intstr/generated.proto"; // Package-wide variables from generator "generated". -option go_package = "v1alpha1"; +option go_package = "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"; message ALBStatus { optional AwsResourceRef loadBalancer = 1; @@ -403,8 +403,13 @@ message CanaryStep { // +optional optional SetCanaryScale setCanaryScale = 5; - // SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service - optional SetHeaderRouting setHeaderRouting = 6; + // SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service + // +optional + optional SetHeaderRoute setHeaderRoute = 6; + + // SetMirrorRoutes Mirrors traffic that matches rules to a particular destination + // +optional + optional SetMirrorRoute setMirrorRoute = 8; } // CanaryStrategy defines parameters for a Replica Based Canary @@ -544,7 +549,7 @@ message CloudWatchMetricStatMetricDimension { // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +kubebuilder:resource:path=clusteranalysistemplates,shortName=cat +// +kubebuilder:resource:path=clusteranalysistemplates,shortName=cat,scope=Cluster // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time since resource was created" message ClusterAnalysisTemplate { optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; @@ -749,6 +754,15 @@ message HeaderRoutingMatch { optional StringMatch headerValue = 2; } +// InfluxdbMetric defines the InfluxDB Flux query to perform canary analysis +message InfluxdbMetric { + // Profile is the name of the secret holding InfluxDB account configuration + optional string profile = 1; + + // Query is a raw InfluxDB flux query to perform + optional string query = 2; +} + // IstioDestinationRule is a reference to an Istio DestinationRule to modify and shape traffic message IstioDestinationRule { // Name holds the name of the DestinationRule @@ -824,6 +838,10 @@ message KayentaThreshold { optional int64 marginal = 2; } +message MangedRoutes { + optional string name = 1; +} + // Measurement is a point in time result value of a single metric, and the time it was measured message Measurement { // Phase is the status of this single measurement @@ -933,6 +951,9 @@ message MetricProvider { // Graphite specifies the Graphite metric to query optional GraphiteMetric graphite = 9; + + // Influxdb specifies the influxdb metric to query + optional InfluxdbMetric influxdb = 10; } // MetricResult contain a list of the most recent measurements for a single metric along with @@ -1412,6 +1433,24 @@ message RolloutTrafficRouting { // Traefik holds specific configuration to use Traefik to route traffic optional TraefikTrafficRouting traefik = 7; + + // A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream + // traffic router. + repeated MangedRoutes managedRoutes = 8; +} + +message RouteMatch { + // Method What http methods should be mirrored + // +optional + optional StringMatch method = 1; + + // Path What url paths should be mirrored + // +optional + optional StringMatch path = 2; + + // Headers What request with matching headers should be mirrored + // +optional + map headers = 3; } // RunSummary contains the final results from the metric executions @@ -1478,9 +1517,28 @@ message SetCanaryScale { optional bool matchTrafficWeight = 3; } -// SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service -message SetHeaderRouting { - repeated HeaderRoutingMatch match = 1; +// SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service +message SetHeaderRoute { + // Name this is the name of the route to use for the mirroring of traffic this also needs + // to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field + optional string name = 1; + + repeated HeaderRoutingMatch match = 2; +} + +message SetMirrorRoute { + // Name this is the name of the route to use for the mirroring of traffic this also needs + // to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field + optional string name = 1; + + // Match Contains a list of rules that if mated will mirror the traffic to the services + // +optional + repeated RouteMatch match = 2; + + // Services The list of services to mirror the traffic to if the method, path, headers match + // Service string `json:"service" protobuf:"bytes,3,opt,name=service"` + // Percentage What percent of the traffic that matched the rules should be mirrored + optional int32 percentage = 4; } message StickinessConfig { diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 977aae1e8e..fa929300aa 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -74,6 +74,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.FieldRef": schema_pkg_apis_rollouts_v1alpha1_FieldRef(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.GraphiteMetric": schema_pkg_apis_rollouts_v1alpha1_GraphiteMetric(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.HeaderRoutingMatch": schema_pkg_apis_rollouts_v1alpha1_HeaderRoutingMatch(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.InfluxdbMetric": schema_pkg_apis_rollouts_v1alpha1_InfluxdbMetric(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.IstioDestinationRule": schema_pkg_apis_rollouts_v1alpha1_IstioDestinationRule(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.IstioTrafficRouting": schema_pkg_apis_rollouts_v1alpha1_IstioTrafficRouting(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.IstioVirtualService": schema_pkg_apis_rollouts_v1alpha1_IstioVirtualService(ref), @@ -81,6 +82,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric": schema_pkg_apis_rollouts_v1alpha1_KayentaMetric(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaScope": schema_pkg_apis_rollouts_v1alpha1_KayentaScope(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaThreshold": schema_pkg_apis_rollouts_v1alpha1_KayentaThreshold(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.MangedRoutes": schema_pkg_apis_rollouts_v1alpha1_MangedRoutes(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.Measurement": schema_pkg_apis_rollouts_v1alpha1_Measurement(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.MeasurementRetention": schema_pkg_apis_rollouts_v1alpha1_MeasurementRetention(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.Metric": schema_pkg_apis_rollouts_v1alpha1_Metric(ref), @@ -110,12 +112,14 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutStatus": schema_pkg_apis_rollouts_v1alpha1_RolloutStatus(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutStrategy": schema_pkg_apis_rollouts_v1alpha1_RolloutStrategy(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutTrafficRouting": schema_pkg_apis_rollouts_v1alpha1_RolloutTrafficRouting(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RouteMatch": schema_pkg_apis_rollouts_v1alpha1_RouteMatch(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RunSummary": schema_pkg_apis_rollouts_v1alpha1_RunSummary(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SMITrafficRouting": schema_pkg_apis_rollouts_v1alpha1_SMITrafficRouting(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ScopeDetail": schema_pkg_apis_rollouts_v1alpha1_ScopeDetail(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SecretKeyRef": schema_pkg_apis_rollouts_v1alpha1_SecretKeyRef(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale": schema_pkg_apis_rollouts_v1alpha1_SetCanaryScale(ref), - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRouting": schema_pkg_apis_rollouts_v1alpha1_SetHeaderRouting(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRoute": schema_pkg_apis_rollouts_v1alpha1_SetHeaderRoute(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute": schema_pkg_apis_rollouts_v1alpha1_SetMirrorRoute(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StickinessConfig": schema_pkg_apis_rollouts_v1alpha1_StickinessConfig(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch": schema_pkg_apis_rollouts_v1alpha1_StringMatch(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TLSRoute": schema_pkg_apis_rollouts_v1alpha1_TLSRoute(ref), @@ -1233,17 +1237,23 @@ func schema_pkg_apis_rollouts_v1alpha1_CanaryStep(ref common.ReferenceCallback) Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale"), }, }, - "setHeaderRouting": { + "setHeaderRoute": { SchemaProps: spec.SchemaProps{ - Description: "SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service", - Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRouting"), + Description: "SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRoute"), + }, + }, + "setMirrorRoute": { + SchemaProps: spec.SchemaProps{ + Description: "SetMirrorRoutes Mirrors traffic that matches rules to a particular destination", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute"), }, }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysis", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutExperimentStep", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutPause", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRouting"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutAnalysis", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutExperimentStep", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutPause", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetCanaryScale", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetHeaderRoute", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SetMirrorRoute"}, } } @@ -2224,7 +2234,6 @@ func schema_pkg_apis_rollouts_v1alpha1_HeaderRoutingMatch(ref common.ReferenceCa "headerValue": { SchemaProps: spec.SchemaProps{ Description: "HeaderValue the value of the header", - Default: map[string]interface{}{}, Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch"), }, }, @@ -2237,6 +2246,33 @@ func schema_pkg_apis_rollouts_v1alpha1_HeaderRoutingMatch(ref common.ReferenceCa } } +func schema_pkg_apis_rollouts_v1alpha1_InfluxdbMetric(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "InfluxdbMetric defines the InfluxDB Flux query to perform canary analysis", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "Profile is the name of the secret holding InfluxDB account configuration", + Type: []string{"string"}, + Format: "", + }, + }, + "query": { + SchemaProps: spec.SchemaProps{ + Description: "Query is a raw InfluxDB flux query to perform", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_rollouts_v1alpha1_IstioDestinationRule(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2534,6 +2570,26 @@ func schema_pkg_apis_rollouts_v1alpha1_KayentaThreshold(ref common.ReferenceCall } } +func schema_pkg_apis_rollouts_v1alpha1_MangedRoutes(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + func schema_pkg_apis_rollouts_v1alpha1_Measurement(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2780,11 +2836,17 @@ func schema_pkg_apis_rollouts_v1alpha1_MetricProvider(ref common.ReferenceCallba Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.GraphiteMetric"), }, }, + "influxdb": { + SchemaProps: spec.SchemaProps{ + Description: "Influxdb specifies the influxdb metric to query", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.InfluxdbMetric"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.CloudWatchMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.DatadogMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.GraphiteMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.NewRelicMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WavefrontMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetric"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.CloudWatchMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.DatadogMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.GraphiteMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.InfluxdbMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.JobMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.KayentaMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.NewRelicMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.PrometheusMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WavefrontMetric", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.WebMetric"}, } } @@ -4159,11 +4221,66 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutTrafficRouting(ref common.Referenc Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TraefikTrafficRouting"), }, }, + "managedRoutes": { + SchemaProps: spec.SchemaProps{ + Description: "A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.MangedRoutes"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ALBTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AmbassadorTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AppMeshTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.IstioTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.MangedRoutes", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.NginxTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SMITrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TraefikTrafficRouting"}, + } +} + +func schema_pkg_apis_rollouts_v1alpha1_RouteMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "method": { + SchemaProps: spec.SchemaProps{ + Description: "Method What http methods should be mirrored", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch"), + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path What url paths should be mirrored", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch"), + }, + }, + "headers": { + SchemaProps: spec.SchemaProps{ + Description: "Headers What request with matching headers should be mirrored", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch"), + }, + }, + }, + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ALBTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AmbassadorTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AppMeshTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.IstioTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.NginxTrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.SMITrafficRouting", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.TraefikTrafficRouting"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.StringMatch"}, } } @@ -4353,13 +4470,20 @@ func schema_pkg_apis_rollouts_v1alpha1_SetCanaryScale(ref common.ReferenceCallba } } -func schema_pkg_apis_rollouts_v1alpha1_SetHeaderRouting(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_rollouts_v1alpha1_SetHeaderRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service", + Description: "SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service", Type: []string{"object"}, Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name this is the name of the route to use for the mirroring of traffic this also needs to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field", + Type: []string{"string"}, + Format: "", + }, + }, "match": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, @@ -4381,6 +4505,50 @@ func schema_pkg_apis_rollouts_v1alpha1_SetHeaderRouting(ref common.ReferenceCall } } +func schema_pkg_apis_rollouts_v1alpha1_SetMirrorRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name this is the name of the route to use for the mirroring of traffic this also needs to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "match": { + SchemaProps: spec.SchemaProps{ + Description: "Match Contains a list of rules that if mated will mirror the traffic to the services", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RouteMatch"), + }, + }, + }, + }, + }, + "percentage": { + SchemaProps: spec.SchemaProps{ + Description: "Services The list of services to mirror the traffic to if the method, path, headers match Service string `json:\"service\" protobuf:\"bytes,3,opt,name=service\"` Percentage What percent of the traffic that matched the rules should be mirrored", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RouteMatch"}, + } +} + func schema_pkg_apis_rollouts_v1alpha1_StickinessConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index f2fffbf9b8..05ac261d5c 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -363,6 +363,15 @@ type RolloutTrafficRouting struct { AppMesh *AppMeshTrafficRouting `json:"appMesh,omitempty" protobuf:"bytes,6,opt,name=appMesh"` // Traefik holds specific configuration to use Traefik to route traffic Traefik *TraefikTrafficRouting `json:"traefik,omitempty" protobuf:"bytes,7,opt,name=traefik"` + // A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream + // traffic router. + ManagedRoutes []MangedRoutes `json:"managedRoutes,omitempty" protobuf:"bytes,8,rep,name=managedRoutes"` +} + +type MangedRoutes struct { + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + //Possibly name for future use + //canaryRoute bool } // TraefikTrafficRouting defines the configuration required to use Traefik as traffic router @@ -557,20 +566,38 @@ type CanaryStep struct { // SetCanaryScale defines how to scale the newRS without changing traffic weight // +optional SetCanaryScale *SetCanaryScale `json:"setCanaryScale,omitempty" protobuf:"bytes,5,opt,name=setCanaryScale"` - // SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service - SetHeaderRouting *SetHeaderRouting `json:"setHeaderRouting,omitempty" protobuf:"bytes,6,opt,name=setHeaderRouting"` + // SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service + // +optional + SetHeaderRoute *SetHeaderRoute `json:"setHeaderRoute,omitempty" protobuf:"bytes,6,opt,name=setHeaderRoute"` + // SetMirrorRoutes Mirrors traffic that matches rules to a particular destination + // +optional + SetMirrorRoute *SetMirrorRoute `json:"setMirrorRoute,omitempty" protobuf:"bytes,8,opt,name=setMirrorRoute"` } -// SetHeaderRouting defines the route with specified header name to send 100% of traffic to the canary service -type SetHeaderRouting struct { - Match []HeaderRoutingMatch `json:"match,omitempty" protobuf:"bytes,1,rep,name=match"` +type SetMirrorRoute struct { + // Name this is the name of the route to use for the mirroring of traffic this also needs + // to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + // Match Contains a list of rules that if mated will mirror the traffic to the services + // +optional + Match []RouteMatch `json:"match,omitempty" protobuf:"bytes,2,opt,name=match"` + + // Services The list of services to mirror the traffic to if the method, path, headers match + //Service string `json:"service" protobuf:"bytes,3,opt,name=service"` + // Percentage What percent of the traffic that matched the rules should be mirrored + Percentage *int32 `json:"percentage,omitempty" protobuf:"varint,4,opt,name=percentage"` } -type HeaderRoutingMatch struct { - // HeaderName the name of the request header - HeaderName string `json:"headerName" protobuf:"bytes,1,opt,name=headerName"` - // HeaderValue the value of the header - HeaderValue StringMatch `json:"headerValue" protobuf:"bytes,2,opt,name=headerValue"` +type RouteMatch struct { + // Method What http methods should be mirrored + // +optional + Method *StringMatch `json:"method,omitempty" protobuf:"bytes,1,opt,name=method"` + // Path What url paths should be mirrored + // +optional + Path *StringMatch `json:"path,omitempty" protobuf:"bytes,2,opt,name=path"` + // Headers What request with matching headers should be mirrored + // +optional + Headers map[string]StringMatch `json:"headers,omitempty" protobuf:"bytes,3,opt,name=headers"` } // StringMatch Used to define what type of matching we will use exact, prefix, or regular expression @@ -583,6 +610,21 @@ type StringMatch struct { Regex string `json:"regex,omitempty" protobuf:"bytes,3,opt,name=regex"` } +// SetHeaderRoute defines the route with specified header name to send 100% of traffic to the canary service +type SetHeaderRoute struct { + // Name this is the name of the route to use for the mirroring of traffic this also needs + // to be included in the `spec.strategy.canary.trafficRouting.managedRoutes` field + Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` + Match []HeaderRoutingMatch `json:"match,omitempty" protobuf:"bytes,2,rep,name=match"` +} + +type HeaderRoutingMatch struct { + // HeaderName the name of the request header + HeaderName string `json:"headerName" protobuf:"bytes,1,opt,name=headerName"` + // HeaderValue the value of the header + HeaderValue *StringMatch `json:"headerValue" protobuf:"bytes,2,opt,name=headerValue"` +} + // SetCanaryScale defines how to scale the newRS without changing traffic weight type SetCanaryScale struct { // Weight sets the percentage of replicas the newRS should have @@ -940,8 +982,13 @@ const ( RolloutReplicaFailure RolloutConditionType = "ReplicaFailure" // RolloutPaused means that rollout is in a paused state. It is still progressing at this point. RolloutPaused RolloutConditionType = "Paused" - // RolloutCompleted means that rollout is in a completed state. It is still progressing at this point. + // RolloutCompleted indicates that the rollout completed its update to the desired revision and is not in the middle + // of any update. Note that a Completed rollout could also be considered Progressing or Degraded, if its Pods become + // unavailable sometime after the update completes. RolloutCompleted RolloutConditionType = "Completed" + // RolloutHealthy means that rollout is in a completed state and is healthy. Which means that all the pods have been updated + // and are passing their health checks and are ready to serve traffic. + RolloutHealthy RolloutConditionType = "Healthy" ) // RolloutCondition describes the state of a rollout at a certain point. diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index 5a3739b95d..e16f370a91 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -708,9 +708,14 @@ func (in *CanaryStep) DeepCopyInto(out *CanaryStep) { *out = new(SetCanaryScale) (*in).DeepCopyInto(*out) } - if in.SetHeaderRouting != nil { - in, out := &in.SetHeaderRouting, &out.SetHeaderRouting - *out = new(SetHeaderRouting) + if in.SetHeaderRoute != nil { + in, out := &in.SetHeaderRoute, &out.SetHeaderRoute + *out = new(SetHeaderRoute) + (*in).DeepCopyInto(*out) + } + if in.SetMirrorRoute != nil { + in, out := &in.SetMirrorRoute, &out.SetMirrorRoute + *out = new(SetMirrorRoute) (*in).DeepCopyInto(*out) } return @@ -1262,7 +1267,11 @@ func (in *GraphiteMetric) DeepCopy() *GraphiteMetric { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HeaderRoutingMatch) DeepCopyInto(out *HeaderRoutingMatch) { *out = *in - out.HeaderValue = in.HeaderValue + if in.HeaderValue != nil { + in, out := &in.HeaderValue, &out.HeaderValue + *out = new(StringMatch) + **out = **in + } return } @@ -1276,6 +1285,22 @@ func (in *HeaderRoutingMatch) DeepCopy() *HeaderRoutingMatch { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfluxdbMetric) DeepCopyInto(out *InfluxdbMetric) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfluxdbMetric. +func (in *InfluxdbMetric) DeepCopy() *InfluxdbMetric { + if in == nil { + return nil + } + out := new(InfluxdbMetric) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IstioDestinationRule) DeepCopyInto(out *IstioDestinationRule) { *out = *in @@ -1427,6 +1452,22 @@ func (in *KayentaThreshold) DeepCopy() *KayentaThreshold { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MangedRoutes) DeepCopyInto(out *MangedRoutes) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MangedRoutes. +func (in *MangedRoutes) DeepCopy() *MangedRoutes { + if in == nil { + return nil + } + out := new(MangedRoutes) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Measurement) DeepCopyInto(out *Measurement) { *out = *in @@ -1563,6 +1604,11 @@ func (in *MetricProvider) DeepCopyInto(out *MetricProvider) { *out = new(GraphiteMetric) **out = **in } + if in.Influxdb != nil { + in, out := &in.Influxdb, &out.Influxdb + *out = new(InfluxdbMetric) + **out = **in + } return } @@ -2225,6 +2271,11 @@ func (in *RolloutTrafficRouting) DeepCopyInto(out *RolloutTrafficRouting) { *out = new(TraefikTrafficRouting) **out = **in } + if in.ManagedRoutes != nil { + in, out := &in.ManagedRoutes, &out.ManagedRoutes + *out = make([]MangedRoutes, len(*in)) + copy(*out, *in) + } return } @@ -2238,6 +2289,39 @@ func (in *RolloutTrafficRouting) DeepCopy() *RolloutTrafficRouting { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RouteMatch) DeepCopyInto(out *RouteMatch) { + *out = *in + if in.Method != nil { + in, out := &in.Method, &out.Method + *out = new(StringMatch) + **out = **in + } + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(StringMatch) + **out = **in + } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = make(map[string]StringMatch, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteMatch. +func (in *RouteMatch) DeepCopy() *RouteMatch { + if in == nil { + return nil + } + out := new(RouteMatch) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RunSummary) DeepCopyInto(out *RunSummary) { *out = *in @@ -2329,22 +2413,52 @@ func (in *SetCanaryScale) DeepCopy() *SetCanaryScale { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SetHeaderRouting) DeepCopyInto(out *SetHeaderRouting) { +func (in *SetHeaderRoute) DeepCopyInto(out *SetHeaderRoute) { *out = *in if in.Match != nil { in, out := &in.Match, &out.Match *out = make([]HeaderRoutingMatch, len(*in)) - copy(*out, *in) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetHeaderRoute. +func (in *SetHeaderRoute) DeepCopy() *SetHeaderRoute { + if in == nil { + return nil + } + out := new(SetHeaderRoute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SetMirrorRoute) DeepCopyInto(out *SetMirrorRoute) { + *out = *in + if in.Match != nil { + in, out := &in.Match, &out.Match + *out = make([]RouteMatch, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Percentage != nil { + in, out := &in.Percentage, &out.Percentage + *out = new(int32) + **out = **in } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetHeaderRouting. -func (in *SetHeaderRouting) DeepCopy() *SetHeaderRouting { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SetMirrorRoute. +func (in *SetMirrorRoute) DeepCopy() *SetMirrorRoute { if in == nil { return nil } - out := new(SetHeaderRouting) + out := new(SetMirrorRoute) in.DeepCopyInto(out) return out } diff --git a/pkg/apis/rollouts/validation/validation.go b/pkg/apis/rollouts/validation/validation.go index 15d10ea7c1..de07fa9581 100644 --- a/pkg/apis/rollouts/validation/validation.go +++ b/pkg/apis/rollouts/validation/validation.go @@ -34,11 +34,13 @@ const ( // InvalidSetCanaryScaleTrafficPolicy indicates that TrafficRouting, required for SetCanaryScale, is missing InvalidSetCanaryScaleTrafficPolicy = "SetCanaryScale requires TrafficRouting to be set" // InvalidSetHeaderRoutingTrafficPolicy indicates that TrafficRouting, required for SetCanaryScale, is missing - InvalidSetHeaderRoutingTrafficPolicy = "SetHeaderRouting requires TrafficRouting, supports Istio only" - // InvalidSetHeaderRoutingMultipleValuePolicy indicates that SetCanaryScale, has multiple values set - InvalidSetHeaderRoutingMultipleValuePolicy = "SetHeaderRouting match value must have one of the following: exact, regex, prefix" - // InvalidSetHeaderRoutingMissedValuePolicy indicates that SetCanaryScale, has multiple values set - InvalidSetHeaderRoutingMissedValuePolicy = "SetHeaderRouting value missed, match value must have one of the following: exact, regex, prefix" + InvalidSetHeaderRoutingTrafficPolicy = "SetHeaderRoute requires TrafficRouting, supports Istio only" + // InvalidSetMirrorRouteTrafficPolicy indicates that TrafficRouting, required for SetCanaryScale, is missing + InvalidSetMirrorRouteTrafficPolicy = "SetMirrorRoute requires TrafficRouting, supports Istio only" + // InvalidStringMatchMultipleValuePolicy indicates that SetCanaryScale, has multiple values set + InvalidStringMatchMultipleValuePolicy = "StringMatch match value must have exactly one of the following: exact, regex, prefix" + // InvalidStringMatchMissedValuePolicy indicates that SetCanaryScale, has multiple values set + InvalidStringMatchMissedValuePolicy = "StringMatch value missed, match value must have one of the following: exact, regex, prefix" // InvalidDurationMessage indicates the Duration value needs to be greater than 0 InvalidDurationMessage = "Duration needs to be greater than 0" // InvalidMaxSurgeMaxUnavailable indicates both maxSurge and MaxUnavailable can not be set to zero @@ -76,6 +78,11 @@ const ( MissedAlbRootServiceMessage = "Root service field is required for the configuration with ALB and ping-pong feature enabled" // PingPongWithAlbOnlyMessage At this moment ping-pong feature works with the ALB traffic routing only PingPongWithAlbOnlyMessage = "Ping-pong feature works with the ALB traffic routing only" + // InvalidStepMissingManagedRoutesField We have a step configured that requires managedRoutes to be configured which is not. + InvalidStepMissingManagedRoutesField = "Step requires spec.strategy.canary.trafficRouting.managedRoutes to be configured" + // InvalideStepRouteNameNotFoundInManagedRoutes A step has been configured that requires managedRoutes and the route name + // is missing from managedRoutes + InvalideStepRouteNameNotFoundInManagedRoutes = "Steps define a route that does not exist in spec.strategy.canary.trafficRouting.managedRoutes" ) // allowAllPodValidationOptions allows all pod options to be true for the purposes of rollout pod @@ -280,9 +287,10 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat for i, step := range canary.Steps { stepFldPath := fldPath.Child("steps").Index(i) allErrs = append(allErrs, hasMultipleStepsType(step, stepFldPath)...) - if step.Experiment == nil && step.Pause == nil && step.SetWeight == nil && step.Analysis == nil && step.SetCanaryScale == nil && step.SetHeaderRouting == nil { - errVal := fmt.Sprintf("step.Experiment: %t step.Pause: %t step.SetWeight: %t step.Analysis: %t step.SetCanaryScale: %t step.SetHeaderRouting: %t", - step.Experiment == nil, step.Pause == nil, step.SetWeight == nil, step.Analysis == nil, step.SetCanaryScale == nil, step.SetHeaderRouting == nil) + if step.Experiment == nil && step.Pause == nil && step.SetWeight == nil && step.Analysis == nil && step.SetCanaryScale == nil && + step.SetHeaderRoute == nil && step.SetMirrorRoute == nil { + errVal := fmt.Sprintf("step.Experiment: %t step.Pause: %t step.SetWeight: %t step.Analysis: %t step.SetCanaryScale: %t step.SetHeaderRoute: %t step.SetMirrorRoutes: %t", + step.Experiment == nil, step.Pause == nil, step.SetWeight == nil, step.Analysis == nil, step.SetCanaryScale == nil, step.SetHeaderRoute == nil, step.SetMirrorRoute == nil) allErrs = append(allErrs, field.Invalid(stepFldPath, errVal, InvalidStepMessage)) } if step.SetWeight != nil && (*step.SetWeight < 0 || *step.SetWeight > 100) { @@ -294,19 +302,57 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat if step.SetCanaryScale != nil && canary.TrafficRouting == nil { allErrs = append(allErrs, field.Required(fldPath.Child("trafficRouting"), InvalidSetCanaryScaleTrafficPolicy)) } - if step.SetHeaderRouting != nil { + + if step.SetHeaderRoute != nil { trafficRouting := rollout.Spec.Strategy.Canary.TrafficRouting if trafficRouting == nil || trafficRouting.Istio == nil { - allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setHeaderRouting"), step.SetHeaderRouting, InvalidSetHeaderRoutingTrafficPolicy)) + allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setHeaderRoute"), step.SetHeaderRoute, InvalidSetHeaderRoutingTrafficPolicy)) } - if step.SetHeaderRouting.Match != nil && len(step.SetHeaderRouting.Match) > 0 { - for j, match := range step.SetHeaderRouting.Match { - matchFld := stepFldPath.Child("setHeaderRouting").Child("match").Index(j) + if step.SetHeaderRoute.Match != nil && len(step.SetHeaderRoute.Match) > 0 { + for j, match := range step.SetHeaderRoute.Match { + matchFld := stepFldPath.Child("setHeaderRoute").Child("match").Index(j) allErrs = append(allErrs, hasMultipleMatchValues(match.HeaderValue, matchFld)...) } } } + if step.SetMirrorRoute != nil { + trafficRouting := rollout.Spec.Strategy.Canary.TrafficRouting + if trafficRouting == nil || trafficRouting.Istio == nil { + allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setMirrorRoute"), step.SetMirrorRoute, "SetMirrorRoute requires TrafficRouting, supports Istio only")) + } + if step.SetMirrorRoute.Match != nil && len(step.SetMirrorRoute.Match) > 0 { + for j, match := range step.SetMirrorRoute.Match { + matchFld := stepFldPath.Child("setMirrorRoute").Child("match").Index(j) + if match.Method != nil { + allErrs = append(allErrs, hasMultipleMatchValues(match.Method, matchFld)...) + } + if match.Path != nil { + allErrs = append(allErrs, hasMultipleMatchValues(match.Path, matchFld)...) + } + if match.Method != nil { + allErrs = append(allErrs, hasMultipleMatchValues(match.Method, matchFld)...) + } + } + } + } + + if rollout.Spec.Strategy.Canary.TrafficRouting != nil { + if step.SetHeaderRoute != nil || step.SetMirrorRoute != nil { + if rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes == nil { + allErrs = append(allErrs, field.Invalid(stepFldPath, step, InvalidStepMissingManagedRoutesField)) + } + } + } + if rollout.Spec.Strategy.Canary.TrafficRouting != nil && rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes != nil { + if step.SetHeaderRoute != nil { + allErrs = append(allErrs, ValidateStepRouteFoundInManagedRoute(stepFldPath.Child("setHeaderRoute"), step.SetHeaderRoute.Name, rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes)...) + } + if step.SetMirrorRoute != nil { + allErrs = append(allErrs, ValidateStepRouteFoundInManagedRoute(stepFldPath.Child("setMirrorRoute"), step.SetMirrorRoute.Name, rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes)...) + } + } + analysisRunArgs := make([]v1alpha1.AnalysisRunArgument, 0) if step.Experiment != nil { for tmplIndex, template := range step.Experiment.Templates { @@ -346,6 +392,20 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat return allErrs } +func ValidateStepRouteFoundInManagedRoute(stepFldPath *field.Path, stepRoutName string, roManagedRoutes []v1alpha1.MangedRoutes) field.ErrorList { + allErrs := field.ErrorList{} + found := false + for _, managedRoute := range roManagedRoutes { + if stepRoutName == managedRoute.Name { + found = true + } + } + if !found { + allErrs = append(allErrs, field.Invalid(stepFldPath, stepRoutName, InvalideStepRouteNameNotFoundInManagedRoutes)) + } + return allErrs +} + func ValidateRolloutStrategyAntiAffinity(antiAffinity *v1alpha1.AntiAffinity, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if antiAffinity != nil { @@ -413,9 +473,16 @@ func hasMultipleStepsType(s v1alpha1.CanaryStep, fldPath *field.Path) field.Erro return allErrs } -func hasMultipleMatchValues(match v1alpha1.StringMatch, fldPath *field.Path) field.ErrorList { +func hasMultipleMatchValues(match *v1alpha1.StringMatch, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - oneOf := make([]bool, 3) + + if match == nil { + e := field.Invalid(fldPath, match, InvalidStringMatchMissedValuePolicy) + allErrs = append(allErrs, e) + return allErrs + } + + var oneOf []bool oneOf = append(oneOf, match.Exact != "") oneOf = append(oneOf, match.Regex != "") oneOf = append(oneOf, match.Prefix != "") @@ -423,7 +490,7 @@ func hasMultipleMatchValues(match v1alpha1.StringMatch, fldPath *field.Path) fie for i := range oneOf { if oneOf[i] { if hasValue { - e := field.Invalid(fldPath, match, InvalidSetHeaderRoutingMultipleValuePolicy) + e := field.Invalid(fldPath, match, InvalidStringMatchMultipleValuePolicy) allErrs = append(allErrs, e) break } @@ -431,7 +498,7 @@ func hasMultipleMatchValues(match v1alpha1.StringMatch, fldPath *field.Path) fie } } if !hasValue { - e := field.Invalid(fldPath, match, InvalidSetHeaderRoutingMissedValuePolicy) + e := field.Invalid(fldPath, match, InvalidStringMatchMissedValuePolicy) allErrs = append(allErrs, e) } return allErrs diff --git a/pkg/apis/rollouts/validation/validation_test.go b/pkg/apis/rollouts/validation/validation_test.go index fead009023..8888ac29ac 100644 --- a/pkg/apis/rollouts/validation/validation_test.go +++ b/pkg/apis/rollouts/validation/validation_test.go @@ -281,7 +281,7 @@ func TestValidateRolloutStrategyAntiAffinity(t *testing.T) { assert.Equal(t, InvalidAntiAffinityWeightMessage, allErrs[0].Detail) } -func TestValidateRolloutStrategyCanary_SetHeaderRoutingIstio(t *testing.T) { +func TestValidateRolloutStrategyCanarySetHeaderRouteIstio(t *testing.T) { ro := &v1alpha1.Rollout{} ro.Spec.Strategy.Canary = &v1alpha1.CanaryStrategy{ CanaryService: "canary", @@ -293,15 +293,15 @@ func TestValidateRolloutStrategyCanary_SetHeaderRoutingIstio(t *testing.T) { }, } - t.Run("using SetHeaderRouting step without the traffic routing", func(t *testing.T) { + t.Run("using SetHeaderRoute step without the traffic routing", func(t *testing.T) { invalidRo := ro.DeepCopy() invalidRo.Spec.Strategy.Canary.TrafficRouting = nil invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ - SetHeaderRouting: &v1alpha1.SetHeaderRouting{ + SetHeaderRoute: &v1alpha1.SetHeaderRoute{ Match: []v1alpha1.HeaderRoutingMatch{ { HeaderName: "agent", - HeaderValue: v1alpha1.StringMatch{Exact: "chrome"}, + HeaderValue: &v1alpha1.StringMatch{Exact: "chrome"}, }, }, }, @@ -310,14 +310,14 @@ func TestValidateRolloutStrategyCanary_SetHeaderRoutingIstio(t *testing.T) { assert.Equal(t, InvalidSetHeaderRoutingTrafficPolicy, allErrs[0].Detail) }) - t.Run("using SetHeaderRouting step with multiple values", func(t *testing.T) { + t.Run("using SetHeaderRoute step with multiple values", func(t *testing.T) { invalidRo := ro.DeepCopy() invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ - SetHeaderRouting: &v1alpha1.SetHeaderRouting{ + SetHeaderRoute: &v1alpha1.SetHeaderRoute{ Match: []v1alpha1.HeaderRoutingMatch{ { HeaderName: "agent", - HeaderValue: v1alpha1.StringMatch{ + HeaderValue: &v1alpha1.StringMatch{ Exact: "chrome", Regex: "chrome(.*)", }, @@ -326,13 +326,13 @@ func TestValidateRolloutStrategyCanary_SetHeaderRoutingIstio(t *testing.T) { }, }} allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) - assert.Equal(t, InvalidSetHeaderRoutingMultipleValuePolicy, allErrs[0].Detail) + assert.Equal(t, InvalidStringMatchMultipleValuePolicy, allErrs[0].Detail) }) - t.Run("using SetHeaderRouting step with missed values", func(t *testing.T) { + t.Run("using SetHeaderRoute step with missed values", func(t *testing.T) { invalidRo := ro.DeepCopy() invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ - SetHeaderRouting: &v1alpha1.SetHeaderRouting{ + SetHeaderRoute: &v1alpha1.SetHeaderRoute{ Match: []v1alpha1.HeaderRoutingMatch{ { HeaderName: "agent", @@ -341,7 +341,153 @@ func TestValidateRolloutStrategyCanary_SetHeaderRoutingIstio(t *testing.T) { }, }} allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) - assert.Equal(t, InvalidSetHeaderRoutingMissedValuePolicy, allErrs[0].Detail) + assert.Equal(t, InvalidStringMatchMissedValuePolicy, allErrs[0].Detail) + }) + + t.Run("using SetHeaderRoute step without managedRoutes defined but missing route", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetHeaderRoute: &v1alpha1.SetHeaderRoute{ + Match: []v1alpha1.HeaderRoutingMatch{ + { + HeaderName: "agent", + HeaderValue: &v1alpha1.StringMatch{Exact: "exact"}, + }, + }, + }, + }} + invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, v1alpha1.MangedRoutes{ + Name: "not-in-steps", + }) + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalideStepRouteNameNotFoundInManagedRoutes, allErrs[0].Detail) + }) +} + +func TestValidateRolloutStrategyCanarySetMirrorRouteIstio(t *testing.T) { + ro := &v1alpha1.Rollout{} + ro.Spec.Strategy.Canary = &v1alpha1.CanaryStrategy{ + CanaryService: "canary", + StableService: "stable", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Istio: &v1alpha1.IstioTrafficRouting{ + VirtualService: &v1alpha1.IstioVirtualService{Name: "virtual-service"}, + }, + }, + } + + t.Run("using SetMirrorRoute step without the traffic routing", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = nil + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: nil, + Percentage: nil, + }, + }} + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalidSetMirrorRouteTrafficPolicy, allErrs[0].Detail) + }) + + t.Run("using SetMirrorRoute step with multiple values", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "test", + Prefix: "test", + }, + Path: nil, + Headers: nil, + }}, + Percentage: nil, + }, + }} + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalidStringMatchMultipleValuePolicy, allErrs[0].Detail) + }) + + t.Run("using SetMirrorRoute step with missed match and no kind", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{}, + Path: nil, + Headers: nil, + }}, + Percentage: nil, + }, + }} + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalidStringMatchMissedValuePolicy, allErrs[0].Detail) + }) + + t.Run("using SetMirrorRoute step without managedRoutes not defined", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "exact", + }, + }}, + Percentage: nil, + }, + }} + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalidStepMissingManagedRoutesField, allErrs[0].Detail) + }) + + t.Run("using SetMirrorRoute step without managedRoutes defined but missing route", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + Path: &v1alpha1.StringMatch{ + Prefix: "/", + }, + }}, + Percentage: nil, + }, + }} + invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, v1alpha1.MangedRoutes{ + Name: "not-in-steps", + }) + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, InvalideStepRouteNameNotFoundInManagedRoutes, allErrs[0].Detail) + }) + + t.Run("using SetMirrorRoute step with managedRoutes defined", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetMirrorRoute: &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + Path: &v1alpha1.StringMatch{ + Prefix: "/", + }, + }}, + Percentage: nil, + }, + }} + invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(invalidRo.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, v1alpha1.MangedRoutes{ + Name: "test-mirror-1", + }) + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Len(t, allErrs, 0) }) } diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index b4c63e2ced..fef1aaafe4 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -61,6 +61,10 @@ func (c *Clientset) Discovery() discovery.DiscoveryInterface { func NewForConfig(c *rest.Config) (*Clientset, error) { configShallowCopy := *c + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + // share the transport between all clients httpClient, err := rest.HTTPClientFor(&configShallowCopy) if err != nil { diff --git a/pkg/kubectl-argo-rollouts/cmd/dashboard/dashboard.go b/pkg/kubectl-argo-rollouts/cmd/dashboard/dashboard.go index b3acb06854..3f3cd1687a 100644 --- a/pkg/kubectl-argo-rollouts/cmd/dashboard/dashboard.go +++ b/pkg/kubectl-argo-rollouts/cmd/dashboard/dashboard.go @@ -35,7 +35,7 @@ func NewCmdDashboard(o *options.ArgoRolloutsOptions) *cobra.Command { } }, } - cmd.Flags().StringVar(&rootPath, "rootPath", "rollouts", "renders the ui url with rootPath prefixed") + cmd.Flags().StringVar(&rootPath, "root-path", "rollouts", "changes the root path of the dashboard") return cmd } diff --git a/rollout/analysis_test.go b/rollout/analysis_test.go index ed21a4c848..46fe961fec 100644 --- a/rollout/analysis_test.go +++ b/rollout/analysis_test.go @@ -153,6 +153,8 @@ func TestCreateBackgroundAnalysisRun(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completeCond, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completeCond) f.rolloutLister = append(f.rolloutLister, r2) f.analysisTemplateLister = append(f.analysisTemplateLister, at) @@ -212,6 +214,8 @@ func TestCreateBackgroundAnalysisRunWithTemplates(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completeCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completeCondition) f.rolloutLister = append(f.rolloutLister, r2) f.analysisTemplateLister = append(f.analysisTemplateLister, at) @@ -272,6 +276,8 @@ func TestCreateBackgroundAnalysisRunWithClusterTemplates(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.clusterAnalysisTemplateLister = append(f.clusterAnalysisTemplateLister, cat) @@ -381,6 +387,8 @@ func TestCreateBackgroundAnalysisRunWithClusterTemplatesAndTemplate(t *testing.T conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.clusterAnalysisTemplateLister = append(f.clusterAnalysisTemplateLister, cat) @@ -446,6 +454,8 @@ func TestCreateAnalysisRunWithCollision(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) ar.Status.Phase = v1alpha1.AnalysisPhaseFailed @@ -514,6 +524,8 @@ func TestCreateAnalysisRunWithCollisionAndSemanticEquality(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.analysisRunLister = append(f.analysisRunLister, ar) @@ -573,6 +585,8 @@ func TestCreateAnalysisRunOnAnalysisStep(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.analysisTemplateLister = append(f.analysisTemplateLister, at) @@ -768,6 +782,8 @@ func TestDoNothingWithAnalysisRunsWhileBackgroundAnalysisRunRunning(t *testing.T conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Canary.CurrentBackgroundAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, Status: v1alpha1.AnalysisPhaseRunning, @@ -816,6 +832,8 @@ func TestDoNothingWhileStepBasedAnalysisRunRunning(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Canary.CurrentStepAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, Status: v1alpha1.AnalysisPhaseRunning, @@ -866,6 +884,8 @@ func TestCancelOlderAnalysisRuns(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Canary.CurrentStepAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, Status: "", @@ -933,6 +953,8 @@ func TestDeleteAnalysisRunsWithNoMatchingRS(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Canary.CurrentStepAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, } @@ -1059,7 +1081,7 @@ func TestIncrementStepAfterSuccessfulAnalysisRun(t *testing.T) { "conditions": %s } }` - condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "") + condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, condition)), patch) } @@ -1128,7 +1150,7 @@ func TestPausedOnInconclusiveBackgroundAnalysisRun(t *testing.T) { "message": "%s" } }` - condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, condition, v1alpha1.PauseReasonInconclusiveAnalysis, now, v1alpha1.PauseReasonInconclusiveAnalysis)), patch) } @@ -1192,7 +1214,7 @@ func TestPausedStepAfterInconclusiveAnalysisRun(t *testing.T) { "message": "%s" } }` - condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + condition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, condition, v1alpha1.PauseReasonInconclusiveAnalysis, now, v1alpha1.PauseReasonInconclusiveAnalysis)), patch) } @@ -1258,7 +1280,7 @@ func TestErrorConditionAfterErrorAnalysisRunStep(t *testing.T) { }` now := timeutil.MetaNow().UTC().Format(time.RFC3339) errmsg := fmt.Sprintf(conditions.RolloutAbortedMessage, 2) + ": " + ar.Status.Message - condition := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, errmsg) + condition := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, errmsg, false) expectedPatch = fmt.Sprintf(expectedPatch, condition, now, errmsg) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) } @@ -1333,7 +1355,7 @@ func TestErrorConditionAfterErrorAnalysisRunBackground(t *testing.T) { } }` errmsg := fmt.Sprintf(conditions.RolloutAbortedMessage, 2) - condition := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "") + condition := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "", false) now := timeutil.Now().UTC().Format(time.RFC3339) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, condition, now, errmsg)), patch) @@ -1386,7 +1408,7 @@ func TestCancelAnalysisRunsWhenAborted(t *testing.T) { assert.True(t, f.verifyPatchedAnalysisRun(cancelOldAr, olderAr)) assert.True(t, f.verifyPatchedAnalysisRun(cancelCurrentAr, ar)) patch := f.getPatchedRollout(patchIndex) - newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "") + newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "", false) expectedPatch := `{ "status": { "conditions": %s, @@ -1488,6 +1510,9 @@ func TestDoNotCreateBackgroundAnalysisRunAfterInconclusiveRun(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + f.rolloutLister = append(f.rolloutLister, r2) f.analysisTemplateLister = append(f.analysisTemplateLister, at) f.objects = append(f.objects, r2, at) @@ -1586,13 +1611,16 @@ func TestCreatePrePromotionAnalysisRun(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} previewSvc := newService("preview", 80, previewSelector, r2) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} @@ -1650,7 +1678,7 @@ func TestDoNotCreatePrePromotionAnalysisAfterPromotionRollout(t *testing.T) { f.analysisTemplateLister = append(f.analysisTemplateLister, at) f.objects = append(f.objects, at) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, false, true, true) r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) f.rolloutLister = append(f.rolloutLister, r2) @@ -1661,7 +1689,7 @@ func TestDoNotCreatePrePromotionAnalysisAfterPromotionRollout(t *testing.T) { f.run(getKey(r2, t)) - newConditions := generateConditionsPatchWithComplete(true, conditions.NewRSAvailableReason, rs2, true, "", true) + newConditions := generateConditionsPatchWithHealthy(true, conditions.NewRSAvailableReason, rs2, true, "", true, true) expectedPatch := fmt.Sprintf(`{ "status":{ "conditions":%s @@ -1723,7 +1751,7 @@ func TestDoNotCreatePrePromotionAnalysisRunOnNotReadyReplicaSet(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 2, 4, 2, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 2, 4, 2, false, true, false) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -1766,7 +1794,7 @@ func TestRolloutPrePromotionAnalysisBecomesInconclusive(t *testing.T) { rs2 := newReplicaSetWithStatus(r2, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) r2.Status.BlueGreen.PrePromotionAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, Status: v1alpha1.AnalysisPhaseRunning, @@ -1777,6 +1805,9 @@ func TestRolloutPrePromotionAnalysisBecomesInconclusive(t *testing.T) { pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -1834,7 +1865,7 @@ func TestRolloutPrePromotionAnalysisSwitchServiceAfterSuccess(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) r2.Status.BlueGreen.PrePromotionAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{ Name: ar.Name, Status: v1alpha1.AnalysisPhaseRunning, @@ -1903,7 +1934,7 @@ func TestRolloutPrePromotionAnalysisHonorAutoPromotionSeconds(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) now := metav1.NewTime(timeutil.MetaNow().Add(-10 * time.Second)) r2.Status.PauseConditions[0].StartTime = now progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") @@ -1966,7 +1997,7 @@ func TestRolloutPrePromotionAnalysisDoNothingOnInconclusiveAnalysis(t *testing.T rs2 := newReplicaSetWithStatus(r2, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) inconclusivePauseCondition := v1alpha1.PauseCondition{ Reason: v1alpha1.PauseReasonInconclusiveAnalysis, StartTime: timeutil.MetaNow(), @@ -2021,12 +2052,15 @@ func TestAbortRolloutOnErrorPrePromotionAnalysis(t *testing.T) { rs2 := newReplicaSetWithStatus(r2, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Phase, r2.Status.Message = rolloututil.CalculateRolloutPhase(r2.Spec, r2.Status) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} @@ -2083,7 +2117,7 @@ func TestCreatePostPromotionAnalysisRun(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -2136,11 +2170,14 @@ func TestRolloutPostPromotionAnalysisSuccess(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 1, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 1, 1, false, true, false) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} activeSvc := newService("active", 80, activeSelector, r2) + cond, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, cond) + f.objects = append(f.objects, r2, at, ar) f.kubeobjects = append(f.kubeobjects, activeSvc, rs1, rs2) f.rolloutLister = append(f.rolloutLister, r2) @@ -2190,7 +2227,7 @@ func TestPostPromotionAnalysisRunHandleInconclusive(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) r2.Status.PauseConditions = []v1alpha1.PauseCondition{{ Reason: v1alpha1.PauseReasonInconclusiveAnalysis, StartTime: timeutil.MetaNow(), @@ -2201,6 +2238,9 @@ func TestPostPromotionAnalysisRunHandleInconclusive(t *testing.T) { pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -2251,13 +2291,16 @@ func TestAbortRolloutOnErrorPostPromotionAnalysis(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} activeSvc := newService("active", 80, activeSelector, r2) diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index 5cae668da3..8ad0b1f838 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -38,15 +38,17 @@ func newBlueGreenRollout(name string, replicas int, revisionHistoryLimit *int32, return rollout } -func TestBlueGreenComplateRolloutRestart(t *testing.T) { +func TestBlueGreenCompletedRolloutRestart(t *testing.T) { f := newFixture(t) defer f.Close() r := newBlueGreenRollout("foo", 1, nil, "active", "preview") r.Status.Conditions = []v1alpha1.RolloutCondition{} - completedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionTrue, conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) - conditions.SetRolloutCondition(&r.Status, *completedCond) + completedHealthyCond := conditions.NewRolloutCondition(v1alpha1.RolloutHealthy, corev1.ConditionFalse, conditions.RolloutHealthyReason, conditions.RolloutNotHealthyMessage) + conditions.SetRolloutCondition(&r.Status, *completedHealthyCond) + completedCond, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r.Status, completedCond) f.rolloutLister = append(f.rolloutLister, r) f.objects = append(f.objects, r) @@ -57,7 +59,7 @@ func TestBlueGreenComplateRolloutRestart(t *testing.T) { rs := newReplicaSet(r, 1) rsPodHash := rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - generatedConditions := generateConditionsPatchWithComplete(false, conditions.ReplicaSetNotAvailableReason, rs, false, "", false) + generatedConditions := generateConditionsPatchWithCompletedHealthy(false, conditions.ReplicaSetUpdatedReason, rs, false, "", false, true) f.expectCreateReplicaSetAction(rs) servicePatchIndex := f.expectPatchServiceAction(previewSvc, rsPodHash) @@ -107,7 +109,7 @@ func TestBlueGreenCreatesReplicaSet(t *testing.T) { rs := newReplicaSet(r, 1) rsPodHash := rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - generatedConditions := generateConditionsPatch(false, conditions.ReplicaSetUpdatedReason, rs, false, "") + generatedConditions := generateConditionsPatchWithCompleted(false, conditions.ReplicaSetUpdatedReason, rs, false, "", true) f.expectCreateReplicaSetAction(rs) servicePatchIndex := f.expectPatchServiceAction(previewSvc, rsPodHash) @@ -271,7 +273,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 2, 4, 2, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 2, 4, 2, false, true, false) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -302,7 +304,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} previewSvc := newService("preview", 80, previewSelector, r2) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} @@ -350,7 +352,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} previewSvc := newService("preview", 80, previewSelector, r2) @@ -373,7 +375,7 @@ func TestBlueGreenHandlePause(t *testing.T) { "conditions": %s } }` - addedConditions := generateConditionsPatchWithPause(true, conditions.RolloutPausedReason, rs2, true, "", true) + addedConditions := generateConditionsPatchWithPause(true, conditions.RolloutPausedReason, rs2, true, "", true, false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, addedConditions)), patch) }) @@ -390,12 +392,15 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Phase, r2.Status.Message = rolloututil.CalculateRolloutPhase(r2.Spec, r2.Status) previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} @@ -433,7 +438,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) now := timeutil.MetaNow() r2.Status.PauseConditions = append(r2.Status.PauseConditions, v1alpha1.PauseCondition{ Reason: v1alpha1.PauseReasonInconclusiveAnalysis, @@ -479,12 +484,15 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, r2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Phase, r2.Status.Message = rolloututil.CalculateRolloutPhase(r2.Spec, r2.Status) previewSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} @@ -517,7 +525,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) now := timeutil.MetaNow() before := metav1.NewTime(now.Add(-1 * time.Minute)) r2.Status.PauseConditions[0].StartTime = before @@ -575,7 +583,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, false) now := timeutil.MetaNow() before := metav1.NewTime(now.Add(-1 * time.Minute)) r2.Status.PauseConditions[0].StartTime = before @@ -586,6 +594,9 @@ func TestBlueGreenHandlePause(t *testing.T) { pausedCondition, _ := newPausedCondition(true) conditions.SetRolloutCondition(&r2.Status, pausedCondition) + + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Phase, r2.Status.Message = rolloututil.CalculateRolloutPhase(r2.Spec, r2.Status) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} @@ -619,7 +630,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) r2.Spec.Strategy.BlueGreen.ScaleDownDelaySeconds = pointer.Int32Ptr(10) progressingCondition, _ := newProgressingCondition(conditions.NewReplicaSetReason, rs2, "") @@ -635,7 +646,7 @@ func TestBlueGreenHandlePause(t *testing.T) { servicePatchIndex := f.expectPatchServiceAction(activeSvc, rs2PodHash) - generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, true, "") + generatedConditions := generateConditionsPatchWithCompleted(true, conditions.ReplicaSetUpdatedReason, rs2, true, "", true) newSelector := metav1.FormatLabelSelector(rs2.Spec.Selector) expectedPatchWithoutSubs := `{ "status": { @@ -670,10 +681,13 @@ func TestBlueGreenHandlePause(t *testing.T) { rs2 := newReplicaSetWithStatus(r2, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) progressingCondition, _ := newProgressingCondition(conditions.NewReplicaSetReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} activeSvc := newService("active", 80, activeSelector, r2) @@ -713,7 +727,7 @@ func TestBlueGreenHandlePause(t *testing.T) { rs1 := newReplicaSetWithStatus(r1, 1, 1) rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r1 = updateBlueGreenRolloutStatus(r1, "", "", "", 1, 1, 1, 1, false, false) + r1 = updateBlueGreenRolloutStatus(r1, "", "", "", 1, 1, 1, 1, false, false, false) activeSelector := map[string]string{"foo": "bar"} @@ -739,7 +753,7 @@ func TestBlueGreenHandlePause(t *testing.T) { } }` - generateConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs1, false, "") + generateConditions := generateConditionsPatchWithCompleted(true, conditions.ReplicaSetUpdatedReason, rs1, false, "", true) newSelector := metav1.FormatLabelSelector(rs1.Spec.Selector) expectedPatch := calculatePatch(r1, fmt.Sprintf(expectedPatchWithoutSubs, rs1PodHash, rs1PodHash, generateConditions, newSelector)) patchRolloutIndex := f.expectPatchRolloutActionWithPatch(r1, expectedPatch) @@ -765,8 +779,10 @@ func TestBlueGreenHandlePause(t *testing.T) { rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] r2.Spec.Strategy.BlueGreen.ScaleDownDelaySeconds = pointer.Int32Ptr(10) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) r2.Status.ControllerPause = true + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) pausedCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, pausedCondition) @@ -788,7 +804,10 @@ func TestBlueGreenHandlePause(t *testing.T) { f.verifyPatchedService(servicePatchIndex, rs2PodHash, "") unpausePatch := f.getPatchedRollout(unpausePatchIndex) - unpauseConditions := generateConditionsPatch(true, conditions.RolloutResumedReason, rs2, true, "") + _, availableCondition := newAvailableCondition(true) + _, progressingCondition := newProgressingCondition(conditions.RolloutResumedReason, rs2, "") + _, compCondition := newCompletedCondition(false) + unpauseConditions := fmt.Sprintf("[%s, %s, %s]", availableCondition, compCondition, progressingCondition) expectedUnpausePatch := `{ "status": { "conditions": %s @@ -796,7 +815,7 @@ func TestBlueGreenHandlePause(t *testing.T) { }` assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedUnpausePatch, unpauseConditions)), unpausePatch) - generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, true, "") + generatedConditions := generateConditionsPatchWithCompleted(true, conditions.ReplicaSetUpdatedReason, rs2, true, "", true) expected2ndPatchWithoutSubs := `{ "status": { "blueGreen": { @@ -835,7 +854,7 @@ func TestBlueGreenAddScaleDownDelayToPreviousActiveReplicaSet(t *testing.T) { f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) r2.Spec.Strategy.BlueGreen.ScaleDownDelaySeconds = pointer.Int32Ptr(10) - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, false) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -858,7 +877,7 @@ func TestBlueGreenAddScaleDownDelayToPreviousActiveReplicaSet(t *testing.T) { } }` newSelector := metav1.FormatLabelSelector(rs2.Spec.Selector) - expectedCondition := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, true, "") + expectedCondition := generateConditionsPatchWithCompleted(true, conditions.ReplicaSetUpdatedReason, rs2, true, "", true) expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchWithoutSubs, rs2PodHash, rs2PodHash, expectedCondition, newSelector)) assert.Equal(t, expectedPatch, patch) } @@ -879,7 +898,7 @@ func TestBlueGreenRolloutStatusHPAStatusFieldsActiveSelectorSet(t *testing.T) { previewSvc := newService("preview", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 0, 0, 0, 0, true, false) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 0, 0, 0, 0, true, false, false) r2.Status.Selector = "" progressingCondition, _ := newProgressingCondition(conditions.RolloutPausedReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) @@ -940,6 +959,7 @@ func TestBlueGreenRolloutStatusHPAStatusFieldsNoActiveSelector(t *testing.T) { assert.Len(t, f.client.Actions(), 1) result := f.client.Actions()[0].(core.PatchAction).GetPatch() _, availableStr := newAvailableCondition(false) + _, compCond := newCompletedCondition(true) expectedPatchWithoutSub := `{ "status":{ "HPAReplicas":1, @@ -947,11 +967,11 @@ func TestBlueGreenRolloutStatusHPAStatusFieldsNoActiveSelector(t *testing.T) { "availableReplicas": 1, "updatedReplicas":1, "replicas":1, - "conditions":[%s, %s], + "conditions":[%s, %s, %s], "selector":"foo=bar" } }` - expectedPatch := calculatePatch(ro, fmt.Sprintf(expectedPatchWithoutSub, progressingConditionStr, availableStr)) + expectedPatch := calculatePatch(ro, fmt.Sprintf(expectedPatchWithoutSub, progressingConditionStr, availableStr, compCond)) assert.Equal(t, expectedPatch, string(result)) } @@ -973,7 +993,7 @@ func TestBlueGreenRolloutScaleUpdateActiveRS(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 1, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 1, 1, false, true, false) f.objects = append(f.objects, r2) previewSvc := newService("preview", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}, r2) @@ -1006,7 +1026,7 @@ func TestPreviewReplicaCountHandleScaleUpPreviewCheckPoint(t *testing.T) { activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 3, 3, 8, 5, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 3, 3, 8, 5, false, true, false) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.kubeobjects = append(f.kubeobjects, activeSvc) @@ -1038,7 +1058,7 @@ func TestPreviewReplicaCountHandleScaleUpPreviewCheckPoint(t *testing.T) { activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 5, 5, 8, 5, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 5, 5, 8, 5, false, true, false) r2.Status.BlueGreen.ScaleUpPreviewCheckPoint = true f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) @@ -1069,7 +1089,7 @@ func TestPreviewReplicaCountHandleScaleUpPreviewCheckPoint(t *testing.T) { activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 5, 5, 8, 5, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 5, 5, 8, 5, false, true, false) r2.Status.BlueGreen.ScaleUpPreviewCheckPoint = true f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) @@ -1103,7 +1123,7 @@ func TestBlueGreenRolloutIgnoringScalingUsePreviewRSCount(t *testing.T) { previewSvc := newService("preview", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) activeSvc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 1, 1, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 2, 1, 1, 1, false, true, true) // Scaling up the rollout r2.Spec.Replicas = pointer.Int32Ptr(2) f.rolloutLister = append(f.rolloutLister, r2) @@ -1136,7 +1156,7 @@ func TestBlueGreenRolloutCompleted(t *testing.T) { s := newService("bar", 80, serviceSelector, r2) f.kubeobjects = append(f.kubeobjects, s) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, false, true, true) r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) f.rolloutLister = append(f.rolloutLister, r2) @@ -1147,7 +1167,7 @@ func TestBlueGreenRolloutCompleted(t *testing.T) { f.run(getKey(r2, t)) - newConditions := generateConditionsPatchWithComplete(true, conditions.NewRSAvailableReason, rs2, true, "", true) + newConditions := generateConditionsPatchWithHealthy(true, conditions.NewRSAvailableReason, rs2, true, "", true, true) expectedPatch := fmt.Sprintf(`{ "status":{ "conditions":%s @@ -1162,7 +1182,7 @@ func TestBlueGreenRolloutCompletedFalse(t *testing.T) { defer f.Close() r1 := newBlueGreenRollout("foo", 1, nil, "bar", "") - completedCondition, _ := newCompletedCondition(true) + completedCondition, _ := newHealthyCondition(true) conditions.SetRolloutCondition(&r1.Status, completedCondition) r2 := bumpVersion(r1) @@ -1182,7 +1202,7 @@ func TestBlueGreenRolloutCompletedFalse(t *testing.T) { s := newService("bar", 80, serviceSelector, r2) f.kubeobjects = append(f.kubeobjects, s) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, true, false) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 1, 1, true, false, false) r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) f.rolloutLister = append(f.rolloutLister, r2) @@ -1197,8 +1217,8 @@ func TestBlueGreenRolloutCompletedFalse(t *testing.T) { err := json.Unmarshal([]byte(patch), &rolloutPatch) assert.NoError(t, err) - index := len(rolloutPatch.Status.Conditions) - 2 - assert.Equal(t, v1alpha1.RolloutCompleted, rolloutPatch.Status.Conditions[index].Type) + index := len(rolloutPatch.Status.Conditions) - 3 + assert.Equal(t, v1alpha1.RolloutHealthy, rolloutPatch.Status.Conditions[index].Type) assert.Equal(t, corev1.ConditionFalse, rolloutPatch.Status.Conditions[index].Status) } @@ -1220,7 +1240,7 @@ func TestBlueGreenUnableToReadScaleDownAt(t *testing.T) { f.kubeobjects = append(f.kubeobjects, s, rs1, rs2) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -1257,7 +1277,7 @@ func TestBlueGreenNotReadyToScaleDownOldReplica(t *testing.T) { f.kubeobjects = append(f.kubeobjects, s, rs1, rs2) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -1290,7 +1310,7 @@ func TestBlueGreenReadyToScaleDownOldReplica(t *testing.T) { f.kubeobjects = append(f.kubeobjects, s, rs1, rs2) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -1335,7 +1355,7 @@ func TestFastRollback(t *testing.T) { r2.Status.CurrentPodHash = rs1PodHash rs1.Annotations[annotations.RevisionAnnotation] = "3" - r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs1PodHash, rs1PodHash, 1, 1, 2, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -1373,7 +1393,7 @@ func TestBlueGreenScaleDownLimit(t *testing.T) { f.kubeobjects = append(f.kubeobjects, s, rs1, rs2, rs3) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2, rs3) - r3 = updateBlueGreenRolloutStatus(r3, "", rs3PodHash, rs3PodHash, 1, 1, 3, 1, false, true) + r3 = updateBlueGreenRolloutStatus(r3, "", rs3PodHash, rs3PodHash, 1, 1, 3, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r3) f.objects = append(f.objects, r3) f.serviceLister = append(f.serviceLister, s) @@ -1412,7 +1432,7 @@ func TestBlueGreenAbort(t *testing.T) { f.kubeobjects = append(f.kubeobjects, s, rs1, rs2) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 1, 1, 2, 1, false, true, true) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) f.serviceLister = append(f.serviceLister, s) @@ -1420,7 +1440,7 @@ func TestBlueGreenAbort(t *testing.T) { f.expectPatchServiceAction(s, rs1PodHash) patchIndex := f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) - expectedConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, true, "") + expectedConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, true, "", false) expectedPatch := fmt.Sprintf(`{ "status": { "blueGreen": { @@ -1449,7 +1469,7 @@ func TestBlueGreenHandlePauseAutoPromoteWithConditions(t *testing.T) { rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true) + r2 = updateBlueGreenRolloutStatus(r2, rs2PodHash, rs1PodHash, rs1PodHash, 1, 1, 2, 1, true, true, true) now := timeutil.MetaNow() before := metav1.NewTime(now.Add(-1 * time.Minute)) r2.Status.PauseConditions[0].StartTime = before @@ -1463,6 +1483,8 @@ func TestBlueGreenHandlePauseAutoPromoteWithConditions(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + //completedCondition, _ := newCompletedCondition(true) + //conditions.SetRolloutCondition(&r2.Status, completedCondition) r2.Status.Phase, r2.Status.Message = rolloututil.CalculateRolloutPhase(r2.Spec, r2.Status) activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} @@ -1481,7 +1503,7 @@ func TestBlueGreenHandlePauseAutoPromoteWithConditions(t *testing.T) { "blueGreen": { "activeSelector": "%s" }, - "conditions": [%s, %s, %s], + "conditions": [%s, %s, %s, %s], "stableRS": "%s", "pauseConditions": null, "controllerPause": null, @@ -1495,9 +1517,12 @@ func TestBlueGreenHandlePauseAutoPromoteWithConditions(t *testing.T) { updatedProgressingCond, _ := newProgressingCondition(conditions.ReplicaSetUpdatedReason, rs2, fmt.Sprintf("ReplicaSet \"%s\" is progressing.", rs2.Name)) progressingCondBytes, err := json.Marshal(updatedProgressingCond) assert.Nil(t, err) - pausedCondBytes, err := json.Marshal(r2.Status.Conditions[2]) + pausedCondBytes, err := json.Marshal(r2.Status.Conditions[3]) + assert.Nil(t, err) + completeCond, _ := newCompletedCondition(true) + completeCondBytes, err := json.Marshal(completeCond) assert.Nil(t, err) - expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchWithoutSubs, rs2PodHash, string(availableCondBytes), string(pausedCondBytes), string(progressingCondBytes), rs2PodHash, rs2PodHash)) + expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchWithoutSubs, rs2PodHash, string(availableCondBytes), string(completeCondBytes), string(pausedCondBytes), string(progressingCondBytes), rs2PodHash, rs2PodHash)) f.expectPatchServiceAction(activeSvc, rs2PodHash) patchRolloutIndex := f.expectPatchRolloutActionWithPatch(r2, expectedPatch) f.run(getKey(r2, t)) @@ -1521,8 +1546,8 @@ func TestBlueGreenAddScaleDownDelay(t *testing.T) { rs2 := newReplicaSetWithStatus(r2, 1, 1) rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true) - completedCondition, _ := newCompletedCondition(true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs2PodHash, 1, 1, 2, 1, false, true, true) + completedCondition, _ := newHealthyCondition(true) conditions.SetRolloutCondition(&r2.Status, completedCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) diff --git a/rollout/canary.go b/rollout/canary.go index d428ca49b9..87228499ab 100644 --- a/rollout/canary.go +++ b/rollout/canary.go @@ -337,7 +337,9 @@ func (c *rolloutContext) completedCurrentCanaryStep() bool { currentStepAr := c.currentArs.CanaryStep analysisExistsAndCompleted := currentStepAr != nil && currentStepAr.Status.Phase.Completed() return analysisExistsAndCompleted && currentStepAr.Status.Phase == v1alpha1.AnalysisPhaseSuccessful - case currentStep.SetHeaderRouting != nil: + case currentStep.SetHeaderRoute != nil: + return true + case currentStep.SetMirrorRoute != nil: return true } return false diff --git a/rollout/canary_test.go b/rollout/canary_test.go index 8cc1c3b0df..56445f3978 100644 --- a/rollout/canary_test.go +++ b/rollout/canary_test.go @@ -178,7 +178,7 @@ func TestCanaryRolloutEnterPauseState(t *testing.T) { } }` - conditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + conditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) now := timeutil.MetaNow().UTC().Format(time.RFC3339) expectedPatchWithoutObservedGen := fmt.Sprintf(expectedPatchTemplate, v1alpha1.PauseReasonCanaryPauseStep, now, conditions, v1alpha1.PauseReasonCanaryPauseStep) expectedPatch := calculatePatch(r2, expectedPatchWithoutObservedGen) @@ -239,6 +239,9 @@ func TestCanaryRolloutUpdatePauseConditionWhilePaused(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + rs1 := newReplicaSetWithStatus(r1, 10, 10) rs2 := newReplicaSetWithStatus(r2, 0, 0) @@ -337,7 +340,7 @@ func TestCanaryRolloutIncrementStepAfterUnPaused(t *testing.T) { "currentStepIndex": 1 } }` - generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "") + generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "", false) expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchTemplate, generatedConditions)) assert.Equal(t, expectedPatch, patch) } @@ -379,7 +382,7 @@ func TestCanaryRolloutUpdateStatusWhenAtEndOfSteps(t *testing.T) { } }` - expectedPatch := fmt.Sprintf(expectedPatchWithoutStableRS, expectedStableRS, generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "")) + expectedPatch := fmt.Sprintf(expectedPatchWithoutStableRS, expectedStableRS, generateConditionsPatchWithCompleted(true, conditions.ReplicaSetUpdatedReason, rs2, false, "", true)) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) } @@ -421,7 +424,7 @@ func TestResetCurrentStepIndexOnStepChange(t *testing.T) { "conditions": %s } }` - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) expectedPatch := fmt.Sprintf(expectedPatchWithoutPodHash, expectedCurrentPodHash, expectedCurrentStepHash, newConditions) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) @@ -462,7 +465,7 @@ func TestResetCurrentStepIndexOnPodSpecChange(t *testing.T) { "conditions": %s } }` - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) expectedPatch := fmt.Sprintf(expectedPatchWithoutPodHash, expectedCurrentPodHash, newConditions) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) @@ -502,7 +505,7 @@ func TestCanaryRolloutCreateFirstReplicasetNoSteps(t *testing.T) { } }` - newConditions := generateConditionsPatch(false, conditions.ReplicaSetUpdatedReason, rs, false, "") + newConditions := generateConditionsPatchWithCompleted(false, conditions.ReplicaSetUpdatedReason, rs, false, "", true) assert.Equal(t, calculatePatch(r, fmt.Sprintf(expectedPatch, newConditions)), patch) } @@ -542,7 +545,7 @@ func TestCanaryRolloutCreateFirstReplicasetWithSteps(t *testing.T) { "conditions": %s } }` - expectedPatch := fmt.Sprintf(expectedPatchWithSub, generateConditionsPatch(false, conditions.ReplicaSetUpdatedReason, rs, false, "")) + expectedPatch := fmt.Sprintf(expectedPatchWithSub, generateConditionsPatchWithCompleted(false, conditions.ReplicaSetUpdatedReason, rs, false, "", true)) assert.Equal(t, calculatePatch(r, expectedPatch), patch) } @@ -840,7 +843,7 @@ func TestRollBackToStable(t *testing.T) { "conditions": %s } }` - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs1, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs1, false, "", true) expectedPatch := fmt.Sprintf(expectedPatchWithoutSub, hash.ComputePodTemplateHash(&r2.Spec.Template, r2.Status.CollisionCount), newConditions) patch := f.getPatchedRollout(patchIndex) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) @@ -883,7 +886,7 @@ func TestGradualShiftToNewStable(t *testing.T) { "conditions": %s } }` - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) expectedPatch := fmt.Sprintf(expectedPatchWithoutSub, newConditions) patch := f.getPatchedRollout(patchIndex) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) @@ -931,7 +934,7 @@ func TestRollBackToStableAndStepChange(t *testing.T) { }` newPodHash := hash.ComputePodTemplateHash(&r2.Spec.Template, r2.Status.CollisionCount) newStepHash := conditions.ComputeStepHash(r2) - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs1, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs1, false, "", true) expectedPatch := fmt.Sprintf(expectedPatchWithoutSub, newPodHash, newStepHash, newConditions) patch := f.getPatchedRollout(patchIndex) assert.Equal(t, calculatePatch(r2, expectedPatch), patch) @@ -969,7 +972,7 @@ func TestCanaryRolloutIncrementStepIfSetWeightsAreCorrect(t *testing.T) { "conditions": %s } }` - newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs3, false, "") + newConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs3, false, "", false) assert.Equal(t, calculatePatch(r3, fmt.Sprintf(expectedPatch, newConditions)), patch) } @@ -1005,6 +1008,9 @@ func TestSyncRolloutWaitAddToQueue(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) @@ -1053,6 +1059,9 @@ func TestSyncRolloutIgnoreWaitOutsideOfReconciliationPeriod(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) @@ -1617,6 +1626,9 @@ func TestHandleNilNewRSOnScaleAndImageChange(t *testing.T) { availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + f.kubeobjects = append(f.kubeobjects, rs1) f.replicaSetLister = append(f.replicaSetLister, rs1) f.rolloutLister = append(f.rolloutLister, r2) @@ -1672,7 +1684,7 @@ func TestHandleCanaryAbort(t *testing.T) { } }` errmsg := fmt.Sprintf(conditions.RolloutAbortedMessage, 2) - newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "") + newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, newConditions, conditions.RolloutAbortedReason, errmsg)), patch) }) @@ -1710,7 +1722,7 @@ func TestHandleCanaryAbort(t *testing.T) { } }` errmsg := fmt.Sprintf(conditions.RolloutAbortedMessage, 1) - newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r1, false, "") + newConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r1, false, "", true) assert.Equal(t, calculatePatch(r1, fmt.Sprintf(expectedPatch, newConditions, conditions.RolloutAbortedReason, errmsg)), patch) }) } diff --git a/rollout/controller.go b/rollout/controller.go index c0434e726a..79c7f9facd 100644 --- a/rollout/controller.go +++ b/rollout/controller.go @@ -8,6 +8,9 @@ import ( "strconv" "time" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts" smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" log "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" @@ -245,6 +248,7 @@ func NewController(cfg ControllerConfig) *Controller { if ro := unstructuredutil.ObjectToRollout(obj); ro != nil { logCtx := logutil.WithRollout(ro) logCtx.Info("rollout deleted") + controller.metricsServer.Remove(ro.Namespace, ro.Name, logutil.RolloutKey) // Rollout is deleted, queue up the referenced Service and/or DestinationRules so // that the rollouts-pod-template-hash can be cleared from each for _, s := range serviceutil.GetRolloutServiceKeys(ro) { @@ -253,6 +257,7 @@ func NewController(cfg ControllerConfig) *Controller { for _, key := range istioutil.GetRolloutDesinationRuleKeys(ro) { controller.IstioController.EnqueueDestinationRule(key) } + controller.recorder.Eventf(ro, record.EventOptions{EventReason: conditions.RolloutDeletedReason}, conditions.RolloutDeletedMessage, ro.Name, ro.Namespace) } }, }) @@ -419,6 +424,19 @@ func (c *Controller) writeBackToInformer(ro *v1alpha1.Rollout) { return } un := unstructured.Unstructured{Object: obj} + // With code-gen tools the argoclientset is generated and the update method here is removing typemetafields + // which the notification controller expects when it converts rolloutobject to toUnstructured and if not present + // and that throws an error "Failed to process: Object 'Kind' is missing in ..." + // Fixing this here as the informer is shared by notification controller by updating typemetafileds. + // TODO: Need to revisit this in the future and maybe we should have a dedicated informer for notification + gvk := un.GetObjectKind().GroupVersionKind() + if len(gvk.Version) == 0 || len(gvk.Group) == 0 || len(gvk.Kind) == 0 { + un.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{ + Group: v1alpha1.SchemeGroupVersion.Group, + Kind: rollouts.RolloutKind, + Version: v1alpha1.SchemeGroupVersion.Version, + }) + } err = c.rolloutsInformer.GetStore().Update(&un) if err != nil { logCtx.Errorf("failed to update informer store: %v", err) diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 99ecf967e1..ec7ac4e355 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -193,6 +193,28 @@ func newPausedCondition(isPaused bool) (v1alpha1.RolloutCondition, string) { return condition, string(conditionBytes) } +func newHealthyCondition(isHealthy bool) (v1alpha1.RolloutCondition, string) { + status := corev1.ConditionTrue + msg := conditions.RolloutHealthyMessage + if !isHealthy { + status = corev1.ConditionFalse + msg = conditions.RolloutNotHealthyMessage + } + condition := v1alpha1.RolloutCondition{ + LastTransitionTime: timeutil.MetaNow(), + LastUpdateTime: timeutil.MetaNow(), + Message: msg, + Reason: conditions.RolloutHealthyReason, + Status: status, + Type: v1alpha1.RolloutHealthy, + } + conditionBytes, err := json.Marshal(condition) + if err != nil { + panic(err) + } + return condition, string(conditionBytes) +} + func newCompletedCondition(isCompleted bool) (v1alpha1.RolloutCondition, string) { status := corev1.ConditionTrue if !isCompleted { @@ -315,33 +337,57 @@ func newAvailableCondition(available bool) (v1alpha1.RolloutCondition, string) { return condition, string(conditionBytes) } -func generateConditionsPatch(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string) string { +func generateConditionsPatch(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isCompleted bool) string { _, availableCondition := newAvailableCondition(available) _, progressingCondition := newProgressingCondition(progressingReason, progressingResource, progressingMessage) + _, completedCondition := newCompletedCondition(isCompleted) if availableConditionFirst { - return fmt.Sprintf("[%s, %s]", availableCondition, progressingCondition) + return fmt.Sprintf("[%s, %s, %s]", availableCondition, progressingCondition, completedCondition) } - return fmt.Sprintf("[%s, %s]", progressingCondition, availableCondition) + return fmt.Sprintf("[%s, %s, %s]", progressingCondition, availableCondition, completedCondition) } -func generateConditionsPatchWithPause(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isPaused bool) string { +func generateConditionsPatchWithPause(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isPaused bool, isCompleted bool) string { _, availableCondition := newAvailableCondition(available) _, progressingCondition := newProgressingCondition(progressingReason, progressingResource, progressingMessage) _, pauseCondition := newPausedCondition(isPaused) + _, completedCondition := newCompletedCondition(isCompleted) + if availableConditionFirst { + return fmt.Sprintf("[%s, %s, %s, %s]", availableCondition, completedCondition, progressingCondition, pauseCondition) + } + return fmt.Sprintf("[%s, %s, %s, %s]", progressingCondition, pauseCondition, availableCondition, completedCondition) +} + +func generateConditionsPatchWithHealthy(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isHealthy bool, isCompleted bool) string { + _, availableCondition := newAvailableCondition(available) + _, progressingCondition := newProgressingCondition(progressingReason, progressingResource, progressingMessage) + _, healthyCondition := newHealthyCondition(isHealthy) + _, completedCondition := newCompletedCondition(isCompleted) if availableConditionFirst { - return fmt.Sprintf("[%s, %s, %s]", availableCondition, progressingCondition, pauseCondition) + return fmt.Sprintf("[%s, %s, %s, %s]", availableCondition, completedCondition, healthyCondition, progressingCondition) } - return fmt.Sprintf("[%s, %s, %s]", progressingCondition, pauseCondition, availableCondition) + return fmt.Sprintf("[%s, %s, %s, %s]", completedCondition, healthyCondition, progressingCondition, availableCondition) } -func generateConditionsPatchWithComplete(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isCompleted bool) string { +func generateConditionsPatchWithCompleted(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isCompleted bool) string { _, availableCondition := newAvailableCondition(available) _, progressingCondition := newProgressingCondition(progressingReason, progressingResource, progressingMessage) _, completeCondition := newCompletedCondition(isCompleted) if availableConditionFirst { - return fmt.Sprintf("[%s, %s, %s]", availableCondition, completeCondition, progressingCondition) + return fmt.Sprintf("[%s, %s, %s]", availableCondition, progressingCondition, completeCondition) + } + return fmt.Sprintf("[%s, %s, %s]", progressingCondition, availableCondition, completeCondition) +} + +func generateConditionsPatchWithCompletedHealthy(available bool, progressingReason string, progressingResource runtime.Object, availableConditionFirst bool, progressingMessage string, isHealthy bool, isCompleted bool) string { + _, completedCondition := newCompletedCondition(isCompleted) + _, availableCondition := newAvailableCondition(available) + _, progressingCondition := newProgressingCondition(progressingReason, progressingResource, progressingMessage) + _, healthyCondition := newHealthyCondition(isHealthy) + if availableConditionFirst { + return fmt.Sprintf("[%s, %s, %s, %s]", availableCondition, healthyCondition, completedCondition, progressingCondition) } - return fmt.Sprintf("[%s, %s, %s]", completeCondition, progressingCondition, availableCondition) + return fmt.Sprintf("[%s, %s, %s, %s]", healthyCondition, completedCondition, progressingCondition, availableCondition) } func updateConditionsPatch(r v1alpha1.Rollout, newCondition v1alpha1.RolloutCondition) string { @@ -351,7 +397,7 @@ func updateConditionsPatch(r v1alpha1.Rollout, newCondition v1alpha1.RolloutCond } // func updateBlueGreenRolloutStatus(r *v1alpha1.Rollout, preview, active string, availableReplicas, updatedReplicas, hpaReplicas int32, pause bool, available bool, progressingStatus string) *v1alpha1.Rollout { -func updateBlueGreenRolloutStatus(r *v1alpha1.Rollout, preview, active, stable string, availableReplicas, updatedReplicas, totalReplicas, hpaReplicas int32, pause bool, available bool) *v1alpha1.Rollout { +func updateBlueGreenRolloutStatus(r *v1alpha1.Rollout, preview, active, stable string, availableReplicas, updatedReplicas, totalReplicas, hpaReplicas int32, pause bool, available bool, isCompleted bool) *v1alpha1.Rollout { newRollout := updateBaseRolloutStatus(r, availableReplicas, updatedReplicas, totalReplicas, hpaReplicas) selector := newRollout.Spec.Selector.DeepCopy() if active != "" { @@ -363,6 +409,8 @@ func updateBlueGreenRolloutStatus(r *v1alpha1.Rollout, preview, active, stable s newRollout.Status.StableRS = stable cond, _ := newAvailableCondition(available) newRollout.Status.Conditions = append(newRollout.Status.Conditions, cond) + completeCond, _ := newCompletedCondition(isCompleted) + newRollout.Status.Conditions = append(newRollout.Status.Conditions, completeCond) if pause { now := timeutil.MetaNow() cond := v1alpha1.PauseCondition{ @@ -1390,6 +1438,8 @@ func TestComputeHashChangeTolerationBlueGreen(t *testing.T) { conditions.SetRolloutCondition(&r.Status, availableCondition) progressingCondition, _ := newProgressingCondition(conditions.ReplicaSetUpdatedReason, rs, "") conditions.SetRolloutCondition(&r.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r.Status, completedCondition) r.Status.Phase, r.Status.Message = rolloututil.CalculateRolloutPhase(r.Spec, r.Status) podTemplate := corev1.PodTemplate{ @@ -1434,6 +1484,8 @@ func TestComputeHashChangeTolerationCanary(t *testing.T) { conditions.SetRolloutCondition(&r.Status, availableCondition) progressingCondition, _ := newProgressingCondition(conditions.ReplicaSetUpdatedReason, rs, "") conditions.SetRolloutCondition(&r.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r.Status, completedCondition) podTemplate := corev1.PodTemplate{ Template: rs.Spec.Template, @@ -1461,7 +1513,7 @@ func TestSwitchBlueGreenToCanary(t *testing.T) { activeSvc := newService("active", 80, nil, r) rs := newReplicaSetWithStatus(r, 1, 1) rsPodHash := rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] - r = updateBlueGreenRolloutStatus(r, "", rsPodHash, rsPodHash, 1, 1, 1, 1, false, true) + r = updateBlueGreenRolloutStatus(r, "", rsPodHash, rsPodHash, 1, 1, 1, 1, false, true, false) // StableRS is set to avoid running the migration code. When .status.canary.stableRS is removed, the line below can be deleted //r.Status.Canary.StableRS = rsPodHash r.Spec.Strategy.BlueGreen = nil @@ -1479,7 +1531,7 @@ func TestSwitchBlueGreenToCanary(t *testing.T) { f.run(getKey(r, t)) patch := f.getPatchedRollout(i) - addedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs, true, "") + addedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs, true, "", true) expectedPatch := fmt.Sprintf(`{ "status": { "blueGreen": { diff --git a/rollout/experiment_test.go b/rollout/experiment_test.go index c8b610a303..e2e50c1f28 100644 --- a/rollout/experiment_test.go +++ b/rollout/experiment_test.go @@ -68,7 +68,7 @@ func TestRolloutCreateExperiment(t *testing.T) { "conditions": %s } }` - conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, ex.Name, conds)), patch) } @@ -125,7 +125,7 @@ func TestRolloutCreateClusterTemplateExperiment(t *testing.T) { "conditions": %s } }` - conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, ex.Name, conds)), patch) } @@ -177,7 +177,7 @@ func TestCreateExperimentWithCollision(t *testing.T) { "conditions": %s } }` - conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, createdEx.Name, conds)), patch) } @@ -228,7 +228,7 @@ func TestCreateExperimentWithCollisionAndSemanticEquality(t *testing.T) { "conditions": %s } }` - conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "") + conds := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, ex.Name, conds)), patch) } @@ -256,6 +256,8 @@ func TestRolloutExperimentProcessingDoNothing(t *testing.T) { conditions.SetRolloutCondition(&r2.Status, progressingCondition) availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.experimentLister = append(f.experimentLister, ex) @@ -311,7 +313,7 @@ func TestAbortRolloutAfterFailedExperiment(t *testing.T) { } }` now := timeutil.Now().UTC().Format(time.RFC3339) - generatedConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "") + generatedConditions := generateConditionsPatch(true, conditions.RolloutAbortedReason, r2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, now, generatedConditions, conditions.RolloutAbortedReason, fmt.Sprintf(conditions.RolloutAbortedMessage, 2))), patch) } @@ -477,7 +479,7 @@ func TestRolloutExperimentFinishedIncrementStep(t *testing.T) { "conditions": %s } }` - generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "") + generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, false, "", false) assert.Equal(t, calculatePatch(r2, fmt.Sprintf(expectedPatch, generatedConditions)), patch) } diff --git a/rollout/mocks/TrafficRoutingReconciler.go b/rollout/mocks/TrafficRoutingReconciler.go index 0e4d6ab1cc..3a5b1fa67f 100644 --- a/rollout/mocks/TrafficRoutingReconciler.go +++ b/rollout/mocks/TrafficRoutingReconciler.go @@ -1,4 +1,4 @@ -// Code generated by mockery v0.0.0-dev. DO NOT EDIT. +// Code generated by mockery v2.14.0. DO NOT EDIT. package mocks @@ -13,13 +13,41 @@ type TrafficRoutingReconciler struct { mock.Mock } -// SetHeaderRouting provides a mock function with given fields: headerRouting -func (_m *TrafficRoutingReconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { - ret := _m.Called(headerRouting) +// RemoveManagedRoutes provides a mock function with given fields: +func (_m *TrafficRoutingReconciler) RemoveManagedRoutes() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetHeaderRoute provides a mock function with given fields: setHeaderRoute +func (_m *TrafficRoutingReconciler) SetHeaderRoute(setHeaderRoute *v1alpha1.SetHeaderRoute) error { + ret := _m.Called(setHeaderRoute) + + var r0 error + if rf, ok := ret.Get(0).(func(*v1alpha1.SetHeaderRoute) error); ok { + r0 = rf(setHeaderRoute) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetMirrorRoute provides a mock function with given fields: setMirrorRoute +func (_m *TrafficRoutingReconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + ret := _m.Called(setMirrorRoute) var r0 error - if rf, ok := ret.Get(0).(func(*v1alpha1.SetHeaderRouting) error); ok { - r0 = rf(headerRouting) + if rf, ok := ret.Get(0).(func(*v1alpha1.SetMirrorRoute) error); ok { + r0 = rf(setMirrorRoute) } else { r0 = ret.Error(0) } @@ -112,3 +140,18 @@ func (_m *TrafficRoutingReconciler) VerifyWeight(desiredWeight int32, additional return r0, r1 } + +type mockConstructorTestingTNewTrafficRoutingReconciler interface { + mock.TestingT + Cleanup(func()) +} + +// NewTrafficRoutingReconciler creates a new instance of TrafficRoutingReconciler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewTrafficRoutingReconciler(t mockConstructorTestingTNewTrafficRoutingReconciler) *TrafficRoutingReconciler { + mock := &TrafficRoutingReconciler{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/rollout/service_test.go b/rollout/service_test.go index 3e0affc53f..8a402d001c 100644 --- a/rollout/service_test.go +++ b/rollout/service_test.go @@ -302,13 +302,15 @@ func TestBlueGreenAWSVerifyTargetGroupsNotYetReady(t *testing.T) { rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] svc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 3, 3, 6, 3, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 3, 3, 6, 3, false, true, false) r2.Status.Message = "" r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) - completedCondition, _ := newCompletedCondition(true) - conditions.SetRolloutCondition(&r2.Status, completedCondition) + completedHealthyCondition, _ := newHealthyCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedHealthyCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2, tgb) @@ -385,13 +387,15 @@ func TestBlueGreenAWSVerifyTargetGroupsReady(t *testing.T) { rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] svc := newService("active", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) - r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 3, 3, 6, 3, false, true) + r2 = updateBlueGreenRolloutStatus(r2, "", rs2PodHash, rs1PodHash, 3, 3, 6, 3, false, true, false) r2.Status.Message = "waiting for post-promotion verification to complete" r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) - completedCondition, _ := newCompletedCondition(true) + completedCondition, _ := newHealthyCondition(true) conditions.SetRolloutCondition(&r2.Status, completedCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + completedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionTrue, conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) + conditions.SetRolloutCondition(&r2.Status, *completedCond) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2, tgb) @@ -459,7 +463,10 @@ func TestCanaryAWSVerifyTargetGroupsNotYetReady(t *testing.T) { } fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) - r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1 := newCanaryRollout("foo", 3, nil, []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }}, pointer.Int32Ptr(0), intstr.FromString("25%"), intstr.FromString("25%")) + r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ ALB: &v1alpha1.ALBTrafficRouting{ Ingress: "ingress", @@ -486,10 +493,12 @@ func TestCanaryAWSVerifyTargetGroupsNotYetReady(t *testing.T) { r2.Status.StableRS = rs2PodHash availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) - completedCondition, _ := newCompletedCondition(false) - conditions.SetRolloutCondition(&r2.Status, completedCondition) + healthyCondition, _ := newHealthyCondition(false) + conditions.SetRolloutCondition(&r2.Status, healthyCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) _, r2.Status.Canary.Weights = calculateWeightStatus(r2, rs2PodHash, rs2PodHash, 0) f.rolloutLister = append(f.rolloutLister, r2) @@ -553,7 +562,9 @@ func TestCanaryAWSVerifyTargetGroupsReady(t *testing.T) { } fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) - r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1 := newCanaryRollout("foo", 3, nil, []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }}, pointer.Int32Ptr(0), intstr.FromString("25%"), intstr.FromString("25%")) r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ ALB: &v1alpha1.ALBTrafficRouting{ Ingress: "ingress", @@ -580,10 +591,12 @@ func TestCanaryAWSVerifyTargetGroupsReady(t *testing.T) { r2.Status.StableRS = rs2PodHash availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) - completedCondition, _ := newCompletedCondition(false) - conditions.SetRolloutCondition(&r2.Status, completedCondition) + healthyCondition, _ := newHealthyCondition(false) + conditions.SetRolloutCondition(&r2.Status, healthyCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) _, r2.Status.Canary.Weights = calculateWeightStatus(r2, rs2PodHash, rs2PodHash, 0) f.rolloutLister = append(f.rolloutLister, r2) @@ -610,7 +623,9 @@ func TestCanaryAWSVerifyTargetGroupsSkip(t *testing.T) { f := newFixture(t) defer f.Close() - r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1 := newCanaryRollout("foo", 3, nil, []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }}, pointer.Int32Ptr(0), intstr.FromString("25%"), intstr.FromString("25%")) r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ ALB: &v1alpha1.ALBTrafficRouting{ Ingress: "ingress", @@ -639,10 +654,12 @@ func TestCanaryAWSVerifyTargetGroupsSkip(t *testing.T) { r2.Status.StableRS = rs2PodHash availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) - completedCondition, _ := newCompletedCondition(false) - conditions.SetRolloutCondition(&r2.Status, completedCondition) + healthyCondition, _ := newHealthyCondition(false) + conditions.SetRolloutCondition(&r2.Status, healthyCondition) progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") conditions.SetRolloutCondition(&r2.Status, progressingCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) _, r2.Status.Canary.Weights = calculateWeightStatus(r2, rs2PodHash, rs2PodHash, 0) f.rolloutLister = append(f.rolloutLister, r2) diff --git a/rollout/sync.go b/rollout/sync.go index fc260f8633..b9bc616552 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -547,15 +547,19 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt isPaused := len(c.rollout.Status.PauseConditions) > 0 || c.rollout.Spec.Paused isAborted := c.pauseContext.IsAborted() - var becameIncomplete bool // remember if we transitioned from completed - completeCond := conditions.GetRolloutCondition(c.rollout.Status, v1alpha1.RolloutCompleted) - if !isPaused && conditions.RolloutComplete(c.rollout, &newStatus) { - updateCompletedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionTrue, conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) - conditions.SetRolloutCondition(&newStatus, *updateCompletedCond) + var becameUnhealthy bool // remember if we transitioned from healthy to unhealthy + completeCond := conditions.GetRolloutCondition(c.rollout.Status, v1alpha1.RolloutHealthy) + if !isPaused && conditions.RolloutHealthy(c.rollout, &newStatus) { + updateHealthyCond := conditions.NewRolloutCondition(v1alpha1.RolloutHealthy, corev1.ConditionTrue, conditions.RolloutHealthyReason, conditions.RolloutHealthyMessage) + conditions.SetRolloutCondition(&newStatus, *updateHealthyCond) + // If we ever wanted to emit a healthy event here it would be noisy and somewhat unpredictable for tests and so should probably be skipped + // when checking in e2e and unit tests. + //c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.RolloutHealthyReason}, conditions.RolloutHealthyMessage) } else { if completeCond != nil { - updateCompletedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionFalse, conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) - becameIncomplete = conditions.SetRolloutCondition(&newStatus, *updateCompletedCond) + updateHealthyCond := conditions.NewRolloutCondition(v1alpha1.RolloutHealthy, corev1.ConditionFalse, conditions.RolloutHealthyReason, conditions.RolloutNotHealthyMessage) + becameUnhealthy = conditions.SetRolloutCondition(&newStatus, *updateHealthyCond) + //c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.RolloutHealthyReason}, conditions.RolloutNotHealthyMessage) } } @@ -576,11 +580,11 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt // In such a case, we should simply not estimate any progress for this rollout. currentCond := conditions.GetRolloutCondition(c.rollout.Status, v1alpha1.RolloutProgressing) - isCompleteRollout := newStatus.Replicas == newStatus.AvailableReplicas && currentCond != nil && currentCond.Reason == conditions.NewRSAvailableReason && currentCond.Type != v1alpha1.RolloutProgressing + isHealthyRollout := newStatus.Replicas == newStatus.AvailableReplicas && currentCond != nil && currentCond.Reason == conditions.NewRSAvailableReason && currentCond.Type != v1alpha1.RolloutProgressing // Check for progress. Only do this if the latest rollout hasn't completed yet and it is not aborted - if !isCompleteRollout && !isAborted { + if !isHealthyRollout && !isAborted { switch { - case conditions.RolloutComplete(c.rollout, &newStatus): + case conditions.RolloutHealthy(c.rollout, &newStatus): // Update the rollout conditions with a message for the new replica set that // was successfully deployed. If the condition already exists, we ignore this update. rsName := "" @@ -590,7 +594,7 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt msg := fmt.Sprintf(conditions.ReplicaSetCompletedMessage, rsName) progressingCondition := conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionTrue, conditions.NewRSAvailableReason, msg) conditions.SetRolloutCondition(&newStatus, *progressingCondition) - case conditions.RolloutProgressing(c.rollout, &newStatus) || becameIncomplete: + case conditions.RolloutProgressing(c.rollout, &newStatus) || becameUnhealthy: // If there is any progress made, continue by not checking if the rollout failed. This // behavior emulates the rolling updater progressDeadline check. msg := fmt.Sprintf(conditions.RolloutProgressingMessage, c.rollout.Name) @@ -599,7 +603,7 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt } var reason string - if newStatus.StableRS == newStatus.CurrentPodHash && becameIncomplete { + if newStatus.StableRS == newStatus.CurrentPodHash && becameUnhealthy { // When a fully promoted rollout becomes Incomplete, e.g., due to the ReplicaSet status changes like // pod restarts, evicted -> recreated, we'll need to reset the rollout's condition to `PROGRESSING` to // avoid any timeouts. @@ -672,6 +676,22 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt } else { conditions.RemoveRolloutCondition(&newStatus, v1alpha1.RolloutReplicaFailure) } + + if conditions.RolloutCompleted(c.rollout, &newStatus) { + // The event gets triggered in function promoteStable + updateCompletedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionTrue, + conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) + conditions.SetRolloutCondition(&newStatus, *updateCompletedCond) + } else { + updateCompletedCond := conditions.NewRolloutCondition(v1alpha1.RolloutCompleted, corev1.ConditionFalse, + conditions.RolloutCompletedReason, conditions.RolloutCompletedReason) + if conditions.SetRolloutCondition(&newStatus, *updateCompletedCond) { + revision, _ := replicasetutil.Revision(c.rollout) + c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.RolloutNotCompletedReason}, + conditions.RolloutNotCompletedMessage, revision+1, newStatus.CurrentPodHash) + } + } + return newStatus } @@ -754,7 +774,7 @@ func (c *rolloutContext) requeueStuckRollout(newStatus v1alpha1.RolloutStatus) t } // No need to estimate progress if the rollout is complete or already timed out. isPaused := len(c.rollout.Status.PauseConditions) > 0 || c.rollout.Spec.Paused - if conditions.RolloutComplete(c.rollout, &newStatus) || currentCond.Reason == conditions.TimedOutReason || isPaused || c.rollout.Status.Abort || isIndefiniteStep(c.rollout) { + if conditions.RolloutHealthy(c.rollout, &newStatus) || currentCond.Reason == conditions.TimedOutReason || isPaused || c.rollout.Status.Abort || isIndefiniteStep(c.rollout) { return time.Duration(-1) } // If there is no sign of progress at this point then there is a high chance that the @@ -923,6 +943,7 @@ func (c *rolloutContext) promoteStable(newStatus *v1alpha1.RolloutStatus, reason } } newStatus.StableRS = newStatus.CurrentPodHash + revision, _ := replicasetutil.Revision(c.rollout) c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.RolloutCompletedReason}, conditions.RolloutCompletedMessage, revision, newStatus.CurrentPodHash, reason) diff --git a/rollout/trafficrouting.go b/rollout/trafficrouting.go index 4e1ff4d305..ebc22b9704 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -3,6 +3,7 @@ package rollout import ( "fmt" "reflect" + "strconv" "strings" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" @@ -126,7 +127,6 @@ func (c *rolloutContext) reconcileTrafficRouting() error { currentStep, index := replicasetutil.GetCurrentCanaryStep(c.rollout) desiredWeight := int32(0) - var setHeaderRouting *v1alpha1.SetHeaderRouting weightDestinations := make([]v1alpha1.WeightDestination, 0) var canaryHash, stableHash string @@ -139,6 +139,10 @@ func (c *rolloutContext) reconcileTrafficRouting() error { if rolloututil.IsFullyPromoted(c.rollout) { // when we are fully promoted. desired canary weight should be 0 + err := reconciler.RemoveManagedRoutes() + if err != nil { + return err + } } else if c.pauseContext.IsAborted() { // when aborted, desired canary weight should immediately be 0 (100% to stable), *unless* // we are using dynamic stable scaling. In that case, we are dynamically decreasing the @@ -151,6 +155,11 @@ func (c *rolloutContext) reconcileTrafficRouting() error { desiredWeight = minInt(desiredWeight, c.rollout.Status.Canary.Weights.Canary.Weight) } } + err := reconciler.RemoveManagedRoutes() + if err != nil { + return err + } + } else if c.newRS == nil || c.newRS.Status.AvailableReplicas == 0 { // when newRS is not available or replicas num is 0. never weight to canary weightDestinations = append(weightDestinations, c.calculateWeightDestinationsFromExperiment()...) @@ -163,9 +172,13 @@ func (c *rolloutContext) reconcileTrafficRouting() error { } else if c.rollout.Status.Canary.Weights != nil { desiredWeight = c.rollout.Status.Canary.Weights.Canary.Weight } + + err := reconciler.RemoveManagedRoutes() + if err != nil { + return err + } } else if index != nil { atDesiredReplicaCount := replicasetutil.AtDesiredReplicaCountsForCanary(c.rollout, c.newRS, c.stableRS, c.otherRSs, nil) - setHeaderRouting = replicasetutil.GetCurrentSetHeaderRouting(c.rollout, *index) if !atDesiredReplicaCount && !c.rollout.Status.PromoteFull { // Use the previous weight since the new RS is not ready for a new weight for i := *index - 1; i >= 0; i-- { @@ -187,6 +200,21 @@ func (c *rolloutContext) reconcileTrafficRouting() error { } } + // We need to check for Generation > 1 because when we first install the rollout we run step 0 this prevents that. + // We could also probably use c.newRS == nil || c.newRS.Status.AvailableReplicas == 0 + if currentStep != nil && c.rollout.ObjectMeta.Generation > 1 { + if currentStep.SetHeaderRoute != nil { + if err = reconciler.SetHeaderRoute(currentStep.SetHeaderRoute); err != nil { + return err + } + } + if currentStep.SetMirrorRoute != nil { + if err = reconciler.SetMirrorRoute(currentStep.SetMirrorRoute); err != nil { + return err + } + } + } + err = reconciler.UpdateHash(canaryHash, stableHash, weightDestinations...) if err != nil { return err @@ -198,10 +226,6 @@ func (c *rolloutContext) reconcileTrafficRouting() error { return err } - if err = reconciler.SetHeaderRouting(setHeaderRouting); err != nil { - return err - } - if modified, newWeights := calculateWeightStatus(c.rollout, canaryHash, stableHash, desiredWeight, weightDestinations...); modified { c.log.Infof("Previous weights: %v", c.rollout.Status.Canary.Weights) c.log.Infof("New weights: %v", newWeights) @@ -209,27 +233,26 @@ func (c *rolloutContext) reconcileTrafficRouting() error { c.newStatus.Canary.Weights = newWeights } - // If we are in the middle of an update at a setWeight step, also perform weight verification. - // Note that we don't do this every reconciliation because weight verification typically involves - // API calls to the cloud provider which could incur rate limiting - shouldVerifyWeight := c.rollout.Status.StableRS != "" && - !rolloututil.IsFullyPromoted(c.rollout) && - currentStep != nil && currentStep.SetWeight != nil + weightVerified, err := reconciler.VerifyWeight(desiredWeight, weightDestinations...) + c.newStatus.Canary.Weights.Verified = weightVerified + if err != nil { + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.WeightVerifyErrorReason}, conditions.WeightVerifyErrorMessage, err) + return nil // return nil instead of error since we want to continue with normal reconciliation + } - if shouldVerifyWeight { - weightVerified, err := reconciler.VerifyWeight(desiredWeight, weightDestinations...) - c.newStatus.Canary.Weights.Verified = weightVerified - if err != nil { - c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.WeightVerifyErrorReason}, conditions.WeightVerifyErrorMessage, err) - return nil // return nil instead of error since we want to continue with normal reconciliation - } - if weightVerified != nil { - if *weightVerified { - c.log.Infof("Desired weight (stepIdx: %d) %d verified", *index, desiredWeight) - } else { - c.log.Infof("Desired weight (stepIdx: %d) %d not yet verified", *index, desiredWeight) - c.enqueueRolloutAfter(c.rollout, defaults.GetRolloutVerifyRetryInterval()) - } + var indexString string + if index != nil { + indexString = strconv.FormatInt(int64(*index), 10) + } else { + indexString = "n/a" + } + + if weightVerified != nil { + if *weightVerified { + c.log.Infof("Desired weight (stepIdx: %s) %d verified", indexString, desiredWeight) + } else { + c.log.Infof("Desired weight (stepIdx: %s) %d not yet verified", indexString, desiredWeight) + c.enqueueRolloutAfter(c.rollout, defaults.GetRolloutVerifyRetryInterval()) } } } diff --git a/rollout/trafficrouting/alb/alb.go b/rollout/trafficrouting/alb/alb.go index bc1f4c7de9..62d1a465d4 100644 --- a/rollout/trafficrouting/alb/alb.go +++ b/rollout/trafficrouting/alb/alb.go @@ -5,6 +5,8 @@ import ( "fmt" "strconv" + rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" + "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -113,11 +115,12 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1 return nil } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } -func (r *Reconciler) shouldVerifyWeight() bool { +// Gets the controller configuration flag for verifying alb weights +func (r *Reconciler) getShouldVerifyWeightCfg() bool { if r.cfg.VerifyWeight != nil { return *r.cfg.VerifyWeight } @@ -125,13 +128,25 @@ func (r *Reconciler) shouldVerifyWeight() bool { } func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) (*bool, error) { - if !r.shouldVerifyWeight() { + if !r.getShouldVerifyWeightCfg() { r.cfg.Status.ALB = nil return nil, nil } + + if !rolloututil.ShouldVerifyWeight(r.cfg.Rollout) { + // If we should not verify weight but the ALB status has not been set yet due to a Rollout resource just being + // installed in the cluster we want to actually run the rest of the function, so we do not return if + // r.cfg.Rollout.Status.ALB is nil. However, if we should not verify, and we have already updated the status once + // we return early to avoid calling AWS apis. + if r.cfg.Rollout.Status.ALB != nil { + return nil, nil + } + } + if r.cfg.Status.ALB == nil { r.cfg.Status.ALB = &v1alpha1.ALBStatus{} } + ctx := context.TODO() rollout := r.cfg.Rollout ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress @@ -292,3 +307,11 @@ func getDesiredAnnotations(current *ingressutil.Ingress, r *v1alpha1.Rollout, po func (r *Reconciler) UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error { return nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/alb/alb_test.go b/rollout/trafficrouting/alb/alb_test.go index 469be0c4b9..7e26f6f16e 100644 --- a/rollout/trafficrouting/alb/alb_test.go +++ b/rollout/trafficrouting/alb/alb_test.go @@ -463,6 +463,10 @@ func (f *fakeAWSClient) getAlbStatus() *v1alpha1.ALBStatus { func TestVerifyWeight(t *testing.T) { newFakeReconciler := func(status *v1alpha1.RolloutStatus) (*Reconciler, *fakeAWSClient) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + ro.Status.StableRS = "a45fe23" + ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }} i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 5, ro.Name, false) i.Status.LoadBalancer = corev1.LoadBalancerStatus{ Ingress: []corev1.LoadBalancerIngress{ @@ -503,6 +507,29 @@ func TestVerifyWeight(t *testing.T) { assert.False(t, *weightVerified) } + // VeryifyWeight not needed + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + status.StableRS = "" + r.cfg.Rollout.Status.StableRS = "" + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.False(t, *weightVerified) + } + + // VeryifyWeight that we do not need to verify weight and status.ALB is already set + { + var status v1alpha1.RolloutStatus + r, _ := newFakeReconciler(&status) + r.cfg.Rollout.Status.ALB = &v1alpha1.ALBStatus{} + r.cfg.Rollout.Status.CurrentStepIndex = nil + r.cfg.Rollout.Spec.Strategy.Canary.Steps = nil + weightVerified, err := r.VerifyWeight(10) + assert.NoError(t, err) + assert.Nil(t, weightVerified) + } + // LoadBalancer found, not at weight { var status v1alpha1.RolloutStatus @@ -642,6 +669,10 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { } newFakeReconciler := func(status *v1alpha1.RolloutStatus) (*Reconciler, *fakeAWSClient) { ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + ro.Status.StableRS = "a45fe23" + ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }} i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 0, ro.Name, false) i.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"] = fmt.Sprintf(actionTemplateWithExperiments, CANARY_SVC, 443, 10, weightDestinations[0].ServiceName, 443, weightDestinations[0].Weight, weightDestinations[1].ServiceName, 443, weightDestinations[1].Weight, STABLE_SVC, 443, 85) @@ -829,3 +860,69 @@ func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { assert.Equal(t, *status.ALB, *fakeClient.getAlbStatus()) } } + +func TestSetHeaderRoute(t *testing.T) { + ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + assert.Len(t, client.Actions(), 0) +} + +func TestSetMirrorRoute(t *testing.T) { + ro := fakeRollout(STABLE_SVC, CANARY_SVC, nil, "ingress", 443) + i := ingress("ingress", STABLE_SVC, CANARY_SVC, STABLE_SVC, 443, 10, ro.Name, false) + client := fake.NewSimpleClientset() + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + ingressWrapper, err := ingressutil.NewIngressWrapper(ingressutil.IngressModeExtensions, client, k8sI) + if err != nil { + t.Fatal(err) + } + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressWrapper: ingressWrapper, + }) + assert.NoError(t, err) + err = r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + assert.Nil(t, err) + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + assert.Len(t, client.Actions(), 0) +} diff --git a/rollout/trafficrouting/ambassador/ambassador.go b/rollout/trafficrouting/ambassador/ambassador.go index 6ed6c56743..5c50f38f26 100644 --- a/rollout/trafficrouting/ambassador/ambassador.go +++ b/rollout/trafficrouting/ambassador/ambassador.go @@ -115,7 +115,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1 return formatErrors(errs) } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } @@ -331,3 +331,11 @@ func (r *Reconciler) sendEvent(eventType, id, msg string) { func (r *Reconciler) UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error { return nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/ambassador/ambassador_test.go b/rollout/trafficrouting/ambassador/ambassador_test.go index 5a558ff25a..bd6e4613de 100644 --- a/rollout/trafficrouting/ambassador/ambassador_test.go +++ b/rollout/trafficrouting/ambassador/ambassador_test.go @@ -519,6 +519,105 @@ func TestReconciler_SetWeight(t *testing.T) { }) } +func TestReconcilerSetHeaderRoute(t *testing.T) { + type fixture struct { + rollout *v1alpha1.Rollout + fakeClient *fakeClient + recorder record.EventRecorder + reconciler *ambassador.Reconciler + } + + setup := func() *fixture { + r := rollout("main-service", "canary-service", []string{"myapp-mapping"}) + fakeClient := &fakeClient{} + rec := record.NewFakeEventRecorder() + l, _ := test.NewNullLogger() + return &fixture{ + rollout: r, + fakeClient: fakeClient, + recorder: rec, + reconciler: &ambassador.Reconciler{ + Rollout: r, + Client: fakeClient, + Recorder: rec, + Log: l.WithContext(context.TODO()), + }, + } + } + t.Run("SetHeaderRoute", func(t *testing.T) { + t.Run("will always return nil", func(t *testing.T) { + // given + t.Parallel() + f := setup() + + // when + err := f.reconciler.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + + // then + assert.Nil(t, err) + + err = f.reconciler.RemoveManagedRoutes() + assert.Nil(t, err) + }) + }) +} + +func TestReconcilerSetMirrorRoute(t *testing.T) { + type fixture struct { + rollout *v1alpha1.Rollout + fakeClient *fakeClient + recorder record.EventRecorder + reconciler *ambassador.Reconciler + } + + setup := func() *fixture { + r := rollout("main-service", "canary-service", []string{"myapp-mapping"}) + fakeClient := &fakeClient{} + rec := record.NewFakeEventRecorder() + l, _ := test.NewNullLogger() + return &fixture{ + rollout: r, + fakeClient: fakeClient, + recorder: rec, + reconciler: &ambassador.Reconciler{ + Rollout: r, + Client: fakeClient, + Recorder: rec, + Log: l.WithContext(context.TODO()), + }, + } + } + t.Run("SetMirrorRoute", func(t *testing.T) { + t.Run("will always return nil", func(t *testing.T) { + // given + t.Parallel() + f := setup() + + // when + err := f.reconciler.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + + // then + assert.Nil(t, err) + + err = f.reconciler.RemoveManagedRoutes() + assert.Nil(t, err) + }) + }) +} + func TestGetMappingService(t *testing.T) { t.Run("will return empty string if service not found", func(t *testing.T) { // given diff --git a/rollout/trafficrouting/appmesh/appmesh.go b/rollout/trafficrouting/appmesh/appmesh.go index cbf2d589f6..f0528f779a 100644 --- a/rollout/trafficrouting/appmesh/appmesh.go +++ b/rollout/trafficrouting/appmesh/appmesh.go @@ -136,7 +136,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1 return nil } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } @@ -391,3 +391,11 @@ func GetRouteRule(route map[string]interface{}) (map[string]interface{}, string, return routeRule, routeType, nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/appmesh/appmesh_test.go b/rollout/trafficrouting/appmesh/appmesh_test.go index d64974bcc7..5f7ed41843 100644 --- a/rollout/trafficrouting/appmesh/appmesh_test.go +++ b/rollout/trafficrouting/appmesh/appmesh_test.go @@ -482,6 +482,63 @@ func TestUpdateHash(t *testing.T) { } } +func TestSetHeaderRoute(t *testing.T) { + t.Run("not implemented check", func(t *testing.T) { + t.Parallel() + client := testutil.NewFakeDynamicClient() + cfg := ReconcilerConfig{ + Rollout: fakeRollout(), + Client: client, + Recorder: record.NewFakeEventRecorder(), + } + r := NewReconciler(cfg) + + err := r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 0) + }) +} + +func TestSetMirrorRoute(t *testing.T) { + t.Run("not implemented check", func(t *testing.T) { + t.Parallel() + client := testutil.NewFakeDynamicClient() + cfg := ReconcilerConfig{ + Rollout: fakeRollout(), + Client: client, + Recorder: record.NewFakeEventRecorder(), + } + r := NewReconciler(cfg) + + err := r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 0) + }) +} + func TestUpdateHashWhenGetStableVirtualNodeFails(t *testing.T) { canaryHash := sampleNewCanaryHash stableHash := sampleNewStableHash diff --git a/rollout/trafficrouting/istio/istio.go b/rollout/trafficrouting/istio/istio.go index b6a63251dd..f001c585a3 100644 --- a/rollout/trafficrouting/istio/istio.go +++ b/rollout/trafficrouting/istio/istio.go @@ -25,7 +25,8 @@ import ( const Http = "http" const Tls = "tls" const Type = "Istio" -const HeaderRouteName = "argo-rollouts-header-based-route" + +const SpecHttpNotFound = "spec.http not found" // NewReconciler returns a reconciler struct that brings the Virtual Service into the desired state func NewReconciler(r *v1alpha1.Rollout, client dynamic.Interface, recorder record.EventRecorder, virtualServiceLister, destinationRuleLister dynamiclister.Lister) *Reconciler { @@ -50,22 +51,6 @@ type Reconciler struct { destinationRuleLister dynamiclister.Lister } -type routePatchAction string - -const ( - DeleteRoute routePatchAction = "DeleteRoute" - InsertHeaderRoute routePatchAction = "InsertRoute" -) - -type virtualServiceRoutePatch struct { - routeIndex int - host string - subset string - patchAction routePatchAction -} - -type virtualServiceRoutePatches []virtualServiceRoutePatch - type virtualServicePatch struct { routeIndex int routeType string @@ -142,6 +127,21 @@ func (r *Reconciler) generateVirtualServicePatches(rolloutVsvcRouteNames []strin stableSubset = r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.StableSubsetName } + // Go through all the routes on the Istio Virtual Service looking for routes that are Istio mirror routes as well as on the + // managedRoutes field on the rollout object so that we can update the Istio mirror upon set weight calls + if r.rollout.Spec.Strategy.Canary.TrafficRouting != nil && r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes != nil { + for _, httpRoute := range httpRoutes { + if httpRoute.Mirror != nil { + for _, managedRoute := range r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes { + //Make sure we only add mirror routes from the managedRoutes field to the list of routes to update for setWeight + if managedRoute.Name == httpRoute.Name { + rolloutVsvcRouteNames = append(rolloutVsvcRouteNames, httpRoute.Name) + } + } + } + } + } + // err can be ignored because we already called ValidateHTTPRoutes earlier httpRouteIndexesToPatch, _ := getHttpRouteIndexesToPatch(rolloutVsvcRouteNames, httpRoutes) tlsRouteIndexesToPatch, _ := getTlsRouteIndexesToPatch(rolloutVsvcTLSRoutes, tlsRoutes) @@ -268,8 +268,7 @@ func (r *Reconciler) reconcileVirtualService(obj *unstructured.Unstructured, vsv // Set HTTP Route Slice if len(httpRoutes) > 0 { - err = unstructured.SetNestedSlice(newObj.Object, httpRoutesI, "spec", Http) - if err != nil { + if err := unstructured.SetNestedSlice(newObj.Object, httpRoutesI, "spec", Http); err != nil { return newObj, len(patches) > 0, err } } @@ -536,7 +535,7 @@ func jsonBytesToDestinationRule(dRuleBytes []byte) (*DestinationRule, error) { func GetHttpRoutesI(obj *unstructured.Unstructured) ([]interface{}, error) { httpRoutesI, notFound, err := unstructured.NestedSlice(obj.Object, "spec", Http) if !notFound { - return nil, fmt.Errorf(".spec.http is not defined") + return nil, fmt.Errorf(SpecHttpNotFound) } if err != nil { return nil, err @@ -547,7 +546,7 @@ func GetHttpRoutesI(obj *unstructured.Unstructured) ([]interface{}, error) { func GetTlsRoutesI(obj *unstructured.Unstructured) ([]interface{}, error) { tlsRoutesI, notFound, err := unstructured.NestedSlice(obj.Object, "spec", Tls) if !notFound { - return nil, fmt.Errorf(".spec.tls is not defined") + return nil, fmt.Errorf(SpecHttpNotFound) } if err != nil { return nil, err @@ -613,6 +612,10 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1 if !modified { continue } + + if err := r.orderRoutes(modifiedVirtualService); err != nil && err.Error() != SpecHttpNotFound { + return fmt.Errorf("[SetWeight] failed to order routes: %w", err) + } _, err = client.Update(ctx, modifiedVirtualService, metav1.UpdateOptions{}) if err == nil { r.log.Debugf("Updated VirtualService: %s", modifiedVirtualService) @@ -649,45 +652,51 @@ func (r *Reconciler) getVirtualService(namespace string, vsvcName string, client return vsvc, err } -func (r *Reconciler) reconcileVirtualServiceRoutes(obj *unstructured.Unstructured, headerRouting *v1alpha1.SetHeaderRouting) (*unstructured.Unstructured, bool, error) { - newObj := obj.DeepCopy() - +func (r *Reconciler) reconcileVirtualServiceHeaderRoutes(obj *unstructured.Unstructured, headerRouting *v1alpha1.SetHeaderRoute) error { // HTTP Routes - var httpRoutes []VirtualServiceHTTPRoute - httpRoutesI, err := GetHttpRoutesI(newObj) - if err == nil { - httpRoutes, err = GetHttpRoutes(httpRoutesI) - if err != nil { - return nil, false, err - } + httpRoutesI, err := GetHttpRoutesI(obj) + if err != nil { + return err } destRuleHost, err := r.getDestinationRuleHost() if err != nil { - return nil, false, err + return err } - // Generate Patches - patches := r.generateHeaderBasedPatches(httpRoutes, headerRouting, destRuleHost) - for _, patch := range patches { - if patch.patchAction == InsertHeaderRoute { - httpRoutesI = append(httpRoutesI[:patch.routeIndex+1], httpRoutesI[patch.routeIndex:]...) - httpRoutesI[patch.routeIndex] = createHeaderRoute(headerRouting, patch) - } else if patch.patchAction == DeleteRoute { - httpRoutesI = append(httpRoutesI[:patch.routeIndex], httpRoutesI[patch.routeIndex+1:]...) - } + canarySvc := r.rollout.Spec.Strategy.Canary.CanaryService + if destRuleHost != "" { + canarySvc = destRuleHost + } + var canarySubset string + if r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule != nil { + canarySubset = r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.CanarySubsetName } - // Set HTTP Route Slice - if len(httpRoutes) > 0 && len(patches) > 0 { - err = unstructured.SetNestedSlice(newObj.Object, httpRoutesI, "spec", Http) + if headerRouting.Match == nil { + //Remove mirror route + err := removeRoute(obj, headerRouting.Name) if err != nil { - return newObj, len(patches) > 0, err + return fmt.Errorf("[reconcileVirtualServiceHeaderRoutes] failed to remove route from virtual service: %w", err) } + return nil } - return newObj, len(patches) > 0, err + + //Remove route first to avoid duplicates + err = removeRoute(obj, headerRouting.Name) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceHeaderRoutes] failed to remove http route from virtual service: %w", err) + } + + httpRoutesI = append(httpRoutesI, createHeaderRoute(headerRouting, canarySvc, canarySubset)) + + err = unstructured.SetNestedSlice(obj.Object, httpRoutesI, "spec", Http) + if err != nil { + return err + } + return nil } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { ctx := context.TODO() virtualServices := r.getVirtualServices() for _, virtualService := range virtualServices { @@ -700,21 +709,23 @@ func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) client := r.client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(namespace) vsvc, err := r.getVirtualService(namespace, vsvcName, client, ctx) if err != nil { - return err + return fmt.Errorf("[SetHeaderRoute] failed to get istio virtual service: %w", err) } - modifiedVirtualService, modified, err := r.reconcileVirtualServiceRoutes(vsvc, headerRouting) + + err = r.reconcileVirtualServiceHeaderRoutes(vsvc, headerRouting) if err != nil { - return err + return fmt.Errorf("[SetHeaderRoute] failed to reconcile header routes: %w", err) } - if !modified { - continue + + if err := r.orderRoutes(vsvc); err != nil && err.Error() != SpecHttpNotFound { + return fmt.Errorf("[SetHeaderRoute] failed to order routes: %w", err) } - _, err = client.Update(ctx, modifiedVirtualService, metav1.UpdateOptions{}) + _, err = client.Update(ctx, vsvc, metav1.UpdateOptions{}) if err == nil { - r.log.Debugf("Updated VirtualService: %s", modifiedVirtualService) - r.recorder.Eventf(r.rollout, record.EventOptions{EventReason: "Updated VirtualService"}, "VirtualService `%s` set headerRouting '%v'", vsvcName, headerRouting) + r.log.Debugf("Updated VirtualService: %s", vsvc) + r.recorder.Eventf(r.rollout, record.EventOptions{EventReason: "Updated VirtualService"}, "VirtualService `%s` set headerRoute '%v'", vsvcName, headerRouting.Name) } else { - return err + return fmt.Errorf("[SetHeaderRoute] failed to update routes: %w", err) } } return nil @@ -754,73 +765,20 @@ func (r *Reconciler) getDestinationRule(dRuleSpec *v1alpha1.IstioDestinationRule return origBytes, dRule, dRuleNew, nil } -func (r *Reconciler) generateHeaderBasedPatches(httpRoutes []VirtualServiceHTTPRoute, headerRouting *v1alpha1.SetHeaderRouting, destRuleHost string) virtualServiceRoutePatches { - canarySvc := r.rollout.Spec.Strategy.Canary.CanaryService - if destRuleHost != "" { - canarySvc = destRuleHost - } - var canarySubset string - if r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule != nil { - canarySubset = r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.CanarySubsetName - } - - patches := virtualServiceRoutePatches{} - headerRouteExist := hasHeaderRoute(httpRoutes) - - if headerRouteExist { - if headerRouting == nil || headerRouting.Match == nil { - deleteHeaderRoute(&patches) - } else { - deleteHeaderRoute(&patches) - insertHeaderRoute(&patches, canarySvc, canarySubset) - } - } else if headerRouting != nil && headerRouting.Match != nil { - insertHeaderRoute(&patches, canarySvc, canarySubset) - } - return patches -} - -func hasHeaderRoute(httpRoutes []VirtualServiceHTTPRoute) bool { - for _, route := range httpRoutes { - if route.Name == HeaderRouteName { - return true - } - } - return false -} - -func deleteHeaderRoute(patches *virtualServiceRoutePatches) { - patch := virtualServiceRoutePatch{ - routeIndex: 0, - patchAction: DeleteRoute, - } - *patches = append(*patches, patch) -} - -func insertHeaderRoute(patches *virtualServiceRoutePatches, host, subset string) { - insertPatch := virtualServiceRoutePatch{ - routeIndex: 0, - host: host, - subset: subset, - patchAction: InsertHeaderRoute, - } - *patches = append(*patches, insertPatch) -} - -func createHeaderRoute(headerRouting *v1alpha1.SetHeaderRouting, patch virtualServiceRoutePatch) map[string]interface{} { +func createHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute, host string, subset string) map[string]interface{} { var routeMatches []interface{} for _, hrm := range headerRouting.Match { - routeMatches = append(routeMatches, createRouteMatch(hrm)) + routeMatches = append(routeMatches, createHeaderRouteMatch(hrm)) } - canaryDestination := routeDestination(patch.host, patch.subset, 100) + canaryDestination := routeDestination(host, subset, 100) return map[string]interface{}{ - "name": HeaderRouteName, + "name": headerRouting.Name, "match": routeMatches, "route": []interface{}{canaryDestination}, } } -func createRouteMatch(hrm v1alpha1.HeaderRoutingMatch) interface{} { +func createHeaderRouteMatch(hrm v1alpha1.HeaderRoutingMatch) interface{} { res := map[string]interface{}{} value := hrm.HeaderValue setMapValueIfNotEmpty(res, "exact", value.Exact) @@ -857,8 +815,14 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations .. // getHttpRouteIndexesToPatch returns array indices of the httpRoutes which need to be patched when updating weights func getHttpRouteIndexesToPatch(routeNames []string, httpRoutes []VirtualServiceHTTPRoute) ([]int, error) { + //We have no routes listed in spec.strategy.canary.trafficRouting.istio.virtualService.routes so find index + //of the first empty named route if len(routeNames) == 0 { - return []int{0}, nil + for i, route := range httpRoutes { + if route.Name == "" { + return []int{i}, nil + } + } } var routeIndexesToPatch []int @@ -955,7 +919,23 @@ func ValidateHTTPRoutes(r *v1alpha1.Rollout, routeNames []string, httpRoutes []V return err } } - if len(routeNames) == 0 && len(httpRoutes) > 1 { + + httpRoutesBytes, err := json.Marshal(httpRoutes) + if err != nil { + return fmt.Errorf("[ValidateHTTPRoutes] failed to marshal http routes: %w", err) + } + var httpRoutesI []interface{} + err = json.Unmarshal(httpRoutesBytes, &httpRoutesI) + if err != nil { + return fmt.Errorf("[ValidateHTTPRoutes] failed to marshal http routes to []interface{}: %w", err) + } + + _, httpRoutesNotWithinManagedRoutes, err := splitManagedRoutesAndNonManagedRoutes(r.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, httpRoutesI) + if err != nil { + return fmt.Errorf("[ValidateHTTPRoutes] failed to split managed and non-managed routes: %w", err) + } + + if len(routeNames) == 0 && len(httpRoutesNotWithinManagedRoutes) > 1 { return fmt.Errorf("spec.http[] should be set in VirtualService and it must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes") } return nil @@ -1029,3 +1009,362 @@ func validateDestinationRule(dRule *v1alpha1.IstioDestinationRule, hasCanarySubs } return nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + ctx := context.TODO() + virtualServices := r.getVirtualServices() + + for _, virtualService := range virtualServices { + name := virtualService.Name + namespace, vsvcName := istioutil.GetVirtualServiceNamespaceName(name) + if namespace == "" { + namespace = r.rollout.Namespace + } + + client := r.client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(namespace) + istioVirtualSvc, err := r.getVirtualService(namespace, vsvcName, client, ctx) + if err != nil { + return fmt.Errorf("[SetMirrorRoute] failed to get virtual service: %w", err) + } + + err = r.reconcileVirtualServiceMirrorRoutes(virtualService, istioVirtualSvc, setMirrorRoute) + if err != nil { + return fmt.Errorf("[SetMirrorRoute] failed reconcile virtual service for mirror routes: %w", err) + } + + if err := r.orderRoutes(istioVirtualSvc); err != nil && err.Error() != SpecHttpNotFound { + return fmt.Errorf("[SetMirrorRoute] failed to order routes based on managedRoute order: %w", err) + } + _, err = client.Update(ctx, istioVirtualSvc, metav1.UpdateOptions{}) + if err == nil { + r.log.Debugf("Updated VirtualService: %s", istioVirtualSvc) + r.recorder.Eventf(r.rollout, record.EventOptions{EventReason: "Updated VirtualService"}, "VirtualService `%s` set mirrorRoute '%v'", vsvcName, setMirrorRoute.Name) + } else { + return fmt.Errorf("[SetMirrorRoute] failed to update virtual service %w", err) + } + + } + return nil +} + +func (r *Reconciler) reconcileVirtualServiceMirrorRoutes(virtualService v1alpha1.IstioVirtualService, istioVirtualService *unstructured.Unstructured, mirrorRoute *v1alpha1.SetMirrorRoute) error { + destRuleHost, err := r.getDestinationRuleHost() + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to get destination rule host: %w", err) + } + canarySvc := r.rollout.Spec.Strategy.Canary.CanaryService + if destRuleHost != "" { + canarySvc = destRuleHost + } + var canarySubset string + if r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule != nil { + canarySubset = r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.CanarySubsetName + } + + //Remove mirror route when there is no match rules we require a match on routes for safety so a none listed match + //acts like a removal of the route instead of say routing all traffic + if mirrorRoute.Match == nil { + //Remove mirror route + err := removeRoute(istioVirtualService, mirrorRoute.Name) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to remove route from virtual service: %w", err) + } + return nil + } + + //Remove route first to avoid duplicates + err = removeRoute(istioVirtualService, mirrorRoute.Name) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to remove http route from virtual service: %w", err) + } + + httpRoutes, _, err := getVirtualServiceHttpRoutes(istioVirtualService) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to get virtual service http routes: %w", err) + } + + mR, err := createMirrorRoute(virtualService, httpRoutes, mirrorRoute, canarySvc, canarySubset) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to create mirror route: %w", err) + } + + vsRoutes, found, err := unstructured.NestedSlice(istioVirtualService.Object, "spec", Http) + if err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to get http routes from virtual service: %w", err) + } + if !found { + return fmt.Errorf(SpecHttpNotFound) + } + vsRoutes = append([]interface{}{mR}, vsRoutes...) + if err := unstructured.SetNestedSlice(istioVirtualService.Object, vsRoutes, "spec", Http); err != nil { + return fmt.Errorf("[reconcileVirtualServiceMirrorRoutes] failed to update virtual service routes via set nested slice: %w", err) + } + + return nil +} + +// getVirtualServiceHttpRoutes This returns all the http routes from an istio virtual service as both a rollouts wrapped type +// []VirtualServiceHTTPRoute and a []interface{} of VirtualServiceHTTPRoute +func getVirtualServiceHttpRoutes(obj *unstructured.Unstructured) ([]VirtualServiceHTTPRoute, []interface{}, error) { + httpRoutesI, err := GetHttpRoutesI(obj) + if err != nil { + return nil, nil, fmt.Errorf("[getVirtualServiceHttpRoutes] failed to get http route interfaces: %w", err) + } + routes, err := GetHttpRoutes(httpRoutesI) + if err != nil { + return nil, httpRoutesI, fmt.Errorf("[getVirtualServiceHttpRoutes] failed to get http route types: %w", err) + } + return routes, httpRoutesI, nil +} + +// createMirrorRoute This returns a map[string]interface{} of an istio virtual service mirror route configuration using the last +// set weight as values for the non-matching destinations and canary service for the matching destination. +func createMirrorRoute(virtualService v1alpha1.IstioVirtualService, httpRoutes []VirtualServiceHTTPRoute, mirrorRouting *v1alpha1.SetMirrorRoute, canarySvc string, subset string) (map[string]interface{}, error) { + var percent int32 + if mirrorRouting.Percentage == nil { + percent = 100 + } else { + percent = *mirrorRouting.Percentage + } + + route, err := getVirtualServiceSetWeightRoute(virtualService.Routes, httpRoutes) + if err != nil { + return nil, fmt.Errorf("[createMirrorRoute] failed to get virtual service http route for keeping non-mirror weights set: %w", err) + } + + var istioMatch []RouteMatch + for _, match := range mirrorRouting.Match { + istioMatch = append(istioMatch, RouteMatch{ + Method: match.Method, + Uri: match.Path, + Headers: match.Headers, + }) + } + + mirrorRoute := map[string]interface{}{ + "name": mirrorRouting.Name, + "match": istioMatch, + "route": route, + "mirror": VirtualServiceDestination{ + Host: canarySvc, + Subset: subset, + }, + "mirrorPercentage": map[string]interface{}{"value": float64(percent)}, + } + + mirrorRouteBytes, err := json.Marshal(mirrorRoute) + if err != nil { + return nil, fmt.Errorf("[createMirrorRoute] failed to marshal mirror route: %w", err) + } + + var mirrorRouteI map[string]interface{} + err = json.Unmarshal(mirrorRouteBytes, &mirrorRouteI) + if err != nil { + return nil, fmt.Errorf("[createMirrorRoute] failed to unmarshal mirror route: %w", err) + } + + return mirrorRouteI, nil +} + +// getVirtualServiceSetWeightRoute This functions goes through the list of Istio Virtual service routes and finds the first +// match from the trafficRouting.istio.virtualService[s].routes field and returns the []VirtualServiceRouteDestination array +// from the istio virtual service this can be useful to get the last set destination percentages on the canary route. +func getVirtualServiceSetWeightRoute(rolloutVsvcRouteNames []string, httpRoutes []VirtualServiceHTTPRoute) ([]VirtualServiceRouteDestination, error) { + routeIndexesToPatch, err := getHttpRouteIndexesToPatch(rolloutVsvcRouteNames, httpRoutes) + if err != nil { + return nil, fmt.Errorf("[getVirtualServiceSetWeightRoute] failed to get routes that need to be patch when set weight is called: %w", err) + } + for _, routeIndex := range routeIndexesToPatch { + route := httpRoutes[routeIndex] + return route.Route, nil + } + return nil, nil +} + +// removeRoute This functions removes the `routeName` route from the Istio Virtual Service +func removeRoute(istioVirtualService *unstructured.Unstructured, routeName string) error { + vsRoutes, found, err := unstructured.NestedSlice(istioVirtualService.Object, "spec", Http) + if err != nil { + return fmt.Errorf("[removeRoute] failed to get http routes from virtual service: %w", err) + } + if !found { + return fmt.Errorf(SpecHttpNotFound) + } + + var newVsRoutes []interface{} + for _, route := range vsRoutes { + routeMap, ok := route.(map[string]interface{}) + if !ok { + return fmt.Errorf("Could not cast type to map[string]interface{} to find route name in Istio Virtual Service") + } + routeNameIstioSvc, ok := routeMap["name"].(string) + if !ok { + log.Debugf("Could not cast type to string to find route name in Istio Virtual Service, route probably has no name set") + } + if routeName != routeNameIstioSvc { + newVsRoutes = append(newVsRoutes, route) + } + } + if err := unstructured.SetNestedSlice(istioVirtualService.Object, newVsRoutes, "spec", Http); err != nil { + return fmt.Errorf("[removeRoute] failed to set http routes on virtual service: %w", err) + } + return nil +} + +// orderRoutes Is a function that orders the routes based on the managedRoute field in the rollout spec. It then places +// the sorted routes ontop of any other route that is already defined on the Istio Virtual Service. +func (r *Reconciler) orderRoutes(istioVirtualService *unstructured.Unstructured) error { + httpRouteI, found, err := unstructured.NestedSlice(istioVirtualService.Object, "spec", Http) + if err != nil { + return fmt.Errorf("[orderRoutes] failed to get virtual service http routes: %w", err) + } + if !found { + return fmt.Errorf(SpecHttpNotFound) + } + + if r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes == nil { + return nil //Not really and error there is just nothing to sort on + } + + managedRoutes := r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes + httpRoutesWithinManagedRoutes, httpRoutesNotWithinManagedRoutes, err := splitManagedRoutesAndNonManagedRoutes(managedRoutes, httpRouteI) + if err != nil { + return fmt.Errorf("[orderRoutes] could not split routes between managed and non managed: %w", err) + } + + finalRoutes, err := getOrderedVirtualServiceRoutes(managedRoutes, httpRoutesWithinManagedRoutes, httpRoutesNotWithinManagedRoutes) + if err != nil { + return fmt.Errorf("[orderRoutes] could not get ordered virtual service routes: %w", err) + } + + if err := unstructured.SetNestedSlice(istioVirtualService.Object, finalRoutes, "spec", Http); err != nil { + return fmt.Errorf("[orderRoutes] set nested slice failed: %w", err) + } + + return nil +} + +// splitManagedRoutesAndNonManagedRoutes This splits the routes from an istio virtual service into two slices +// one slice contains all the routes that are also in the rollouts managedRoutes object and one that contains routes +// that where only in the virtual service (aka routes that where manually added by user) +func splitManagedRoutesAndNonManagedRoutes(managedRoutes []v1alpha1.MangedRoutes, httpRouteI []interface{}) (httpRoutesWithinManagedRoutes []VirtualServiceHTTPRoute, httpRoutesNotWithinManagedRoutes []VirtualServiceHTTPRoute, err error) { + var httpRoutes []VirtualServiceHTTPRoute + + jsonHttpRoutes, err := json.Marshal(httpRouteI) + if err != nil { + return nil, nil, fmt.Errorf("[splitManagedRoutesAndNonManagedRoutes] failed to marsharl http route interface: %w", err) + } + + if err := json.Unmarshal(jsonHttpRoutes, &httpRoutes); err != nil { + return nil, nil, fmt.Errorf("[splitManagedRoutesAndNonManagedRoutes] failed to unmarsharl http route interface: %w", err) + } + + for _, route := range httpRoutes { + var found bool = false + for _, managedRoute := range managedRoutes { + if route.Name == managedRoute.Name { + httpRoutesWithinManagedRoutes = append(httpRoutesWithinManagedRoutes, route) + found = true + break + } + } + if !found { + httpRoutesNotWithinManagedRoutes = append(httpRoutesNotWithinManagedRoutes, route) + } + } + + return httpRoutesWithinManagedRoutes, httpRoutesNotWithinManagedRoutes, nil +} + +// getOrderedVirtualServiceRoutes This returns an []interface{} of istio virtual routes where the routes are ordered based +// on the rollouts managedRoutes field. We take the routes from the rollouts managedRoutes field order them and place them on top +// of routes that are manually defined within the virtual service (aka. routes that users have defined manually) +func getOrderedVirtualServiceRoutes(managedRoutes []v1alpha1.MangedRoutes, httpRoutesWithinManagedRoutes []VirtualServiceHTTPRoute, httpRoutesNotWithinManagedRoutes []VirtualServiceHTTPRoute) ([]interface{}, error) { + var orderedManagedRoutes []VirtualServiceHTTPRoute + for _, route := range managedRoutes { + for _, managedRoute := range httpRoutesWithinManagedRoutes { + if route.Name == managedRoute.Name { + orderedManagedRoutes = append(orderedManagedRoutes, managedRoute) + } + } + } + + allIstioRoutes := append(orderedManagedRoutes, httpRoutesNotWithinManagedRoutes...) + + jsonAllIstioRoutes, err := json.Marshal(allIstioRoutes) + if err != nil { + return nil, fmt.Errorf("[getOrderedVirtualServiceRoutes] failed to marsharl istio routes: %w", err) + } + var orderedRoutes []interface{} + if err := json.Unmarshal(jsonAllIstioRoutes, &orderedRoutes); err != nil { + return nil, fmt.Errorf("[getOrderedVirtualServiceRoutes] failed to unmarsharl istio routes: %w", err) + } + + return orderedRoutes, nil +} + +// RemoveManagedRoutes this removes all the routes in all the istio virtual services rollouts is managing by getting two slices +// from the splitManagedRoutesAndNonManagedRoutes function and setting the Istio Virtual Service routes to just the ones not managed +// by rollouts +func (r *Reconciler) RemoveManagedRoutes() error { + ctx := context.TODO() + virtualServices := r.getVirtualServices() + + for _, virtualService := range virtualServices { + name := virtualService.Name + namespace, vsvcName := istioutil.GetVirtualServiceNamespaceName(name) + if namespace == "" { + namespace = r.rollout.Namespace + } + + client := r.client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(namespace) + istioVirtualService, err := r.getVirtualService(namespace, vsvcName, client, ctx) + if err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to get virtual service: %w", err) + } + + httpRouteI, found, err := unstructured.NestedSlice(istioVirtualService.Object, "spec", Http) + if err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to get http routes from virtual service: %w", err) + } + if !found { + return fmt.Errorf("[RemoveManagedRoutes] %s: %w", SpecHttpNotFound, err) + } + + managedRoutes := r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes + if len(managedRoutes) == 0 { + return nil + } + httpRoutesWithinManagedRoutes, httpRoutesNotWithinManagedRoutes, err := splitManagedRoutesAndNonManagedRoutes(managedRoutes, httpRouteI) + if err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to split managaed and non-managed routes: %w", err) + } + + if len(httpRoutesWithinManagedRoutes) == 0 { + //no routes to remove + return nil + } + + jsonNonManagedRoutes, err := json.Marshal(httpRoutesNotWithinManagedRoutes) + if err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to marshal non-managed routes: %w", err) + } + var nonManagedRoutesI []interface{} + if err := json.Unmarshal(jsonNonManagedRoutes, &nonManagedRoutesI); err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to split managaed and non-managed routes: %w", err) + } + + if err := unstructured.SetNestedSlice(istioVirtualService.Object, nonManagedRoutesI, "spec", Http); err != nil { + return fmt.Errorf("[RemoveManagedRoutes] failed to set nested slice on virtual service to remove managed routes: %w", err) + } + + _, err = client.Update(ctx, istioVirtualService, metav1.UpdateOptions{}) + if err == nil { + r.log.Debugf("Updated VirtualService: %s", istioVirtualService) + r.recorder.Eventf(r.rollout, record.EventOptions{EventReason: "Updated VirtualService"}, "VirtualService `%s` removed all managed routes.", vsvcName) + } else { + return fmt.Errorf("[RemoveManagedRoutes] failed to update kubernetes virtual service: %w", err) + } + } + return nil +} diff --git a/rollout/trafficrouting/istio/istio_test.go b/rollout/trafficrouting/istio/istio_test.go index 939c16beb0..3c57dcfbdb 100644 --- a/rollout/trafficrouting/istio/istio_test.go +++ b/rollout/trafficrouting/istio/istio_test.go @@ -486,90 +486,156 @@ func TestHttpReconcileWeightsBaseCase(t *testing.T) { } } -func TestHttpReconcileHeaderRoute_HostBased(t *testing.T) { - r := &Reconciler{ - rollout: rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}), - } +func TestHttpReconcileHeaderRouteHostBased(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) + client := testutil.NewFakeDynamicClient(obj) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + const headerName = "test-header-route" + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: headerName, + }, + }...) // Test for both the HTTP VS & Mixed VS - vsObj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) - hr := &v1alpha1.SetHeaderRouting{ + hr := &v1alpha1.SetHeaderRoute{ + Name: headerName, Match: []v1alpha1.HeaderRoutingMatch{ { HeaderName: "agent", - HeaderValue: v1alpha1.StringMatch{Exact: "firefox"}, + HeaderValue: &v1alpha1.StringMatch{Exact: "firefox"}, }, }, } - modifiedVsObj, _, err := r.reconcileVirtualServiceRoutes(vsObj, hr) + + err := r.SetHeaderRoute(hr) assert.Nil(t, err) - assert.NotNil(t, modifiedVsObj) + + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) // HTTP Routes - httpRoutes := extractHttpRoutes(t, modifiedVsObj) + httpRoutes := extractHttpRoutes(t, iVirtualService) // Assertions - assert.Equal(t, httpRoutes[0].Name, HeaderRouteName) + assert.Equal(t, httpRoutes[0].Name, headerName) checkDestination(t, httpRoutes[0].Route, "canary", 100) assert.Equal(t, len(httpRoutes[0].Route), 1) assert.Equal(t, httpRoutes[1].Name, "primary") checkDestination(t, httpRoutes[1].Route, "stable", 100) assert.Equal(t, httpRoutes[2].Name, "secondary") - // Reset header routing, expecting removing of the header route - - modifiedVsObj, _, err = r.reconcileVirtualServiceRoutes(vsObj, nil) + err = r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: headerName, + }) assert.Nil(t, err) - assert.NotNil(t, modifiedVsObj) + + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) // HTTP Routes - httpRoutes = extractHttpRoutes(t, modifiedVsObj) + httpRoutes = extractHttpRoutes(t, iVirtualService) // Assertions assert.Equal(t, httpRoutes[0].Name, "primary") assert.Equal(t, httpRoutes[1].Name, "secondary") } -func TestHttpReconcileHeaderRoute_SubsetBased(t *testing.T) { +func TestHttpReconcileHeaderRouteSubsetBased(t *testing.T) { ro := rolloutWithDestinationRule() - obj := unstructuredutil.StrToUnstructuredUnsafe(` + const RolloutService = "rollout-service" + const StableSubsetName = "stable-subset" + const CanarySubsetName = "canary-subset" + ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name = "vsvc" + ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes = nil + ro.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.StableSubsetName = StableSubsetName + ro.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.CanarySubsetName = CanarySubsetName + dRule := unstructuredutil.StrToUnstructuredUnsafe(` apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: istio-destrule namespace: default spec: - host: root-service + host: rollout-service subsets: - - name: stable - - name: canary + - name: stable-subset + - name: canary-subset `) - client := testutil.NewFakeDynamicClient(obj) - r := NewReconciler(ro, client, record.NewFakeEventRecorder(), nil, nil) + obj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteSubsetVsvc) + client := testutil.NewFakeDynamicClient(obj, dRule) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() - // Test for both the HTTP VS & Mixed VS - vsObj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteSubsetVsvc) - hr := &v1alpha1.SetHeaderRouting{ + const headerName = "test-header-route" + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: headerName, + }, + }...) + + hr := &v1alpha1.SetHeaderRoute{ + Name: headerName, Match: []v1alpha1.HeaderRoutingMatch{ { HeaderName: "agent", - HeaderValue: v1alpha1.StringMatch{ + HeaderValue: &v1alpha1.StringMatch{ Regex: "firefox", }, }, }, } - modifiedVsObj, _, err := r.reconcileVirtualServiceRoutes(vsObj, hr) + + err := r.SetHeaderRoute(hr) assert.Nil(t, err) - assert.NotNil(t, modifiedVsObj) + + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) // HTTP Routes - httpRoutes := extractHttpRoutes(t, modifiedVsObj) + httpRoutes := extractHttpRoutes(t, iVirtualService) // Assertions - assert.Equal(t, httpRoutes[0].Name, HeaderRouteName) - assert.Equal(t, httpRoutes[0].Route[0].Destination.Host, "root-service") - assert.Equal(t, httpRoutes[0].Route[0].Destination.Subset, "canary") + assert.Equal(t, httpRoutes[0].Name, headerName) + assert.Equal(t, httpRoutes[0].Route[0].Destination.Host, "rollout-service") + assert.Equal(t, httpRoutes[0].Route[0].Destination.Subset, "canary-subset") +} + +func TestReconcileUpdateHeader(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, v1alpha1.MangedRoutes{ + Name: "test-mirror-1", + }) + AssertReconcileUpdateHeader(t, regularVsvc, ro) +} +func AssertReconcileUpdateHeader(t *testing.T, vsvc string, ro *v1alpha1.Rollout) *dynamicfake.FakeDynamicClient { + obj := unstructuredutil.StrToUnstructuredUnsafe(vsvc) + client := testutil.NewFakeDynamicClient(obj) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + var setHeader = &v1alpha1.SetHeaderRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.HeaderRoutingMatch{ + { + HeaderName: "browser", + HeaderValue: &v1alpha1.StringMatch{ + Prefix: "Firefox", + }, + }, + }, + } + err := r.SetHeaderRoute(setHeader) + + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 1) + assert.Equal(t, "update", actions[0].GetVerb()) + return client } func TestTlsReconcileWeightsBaseCase(t *testing.T) { @@ -1719,3 +1785,385 @@ func TestMultipleVirtualServiceReconcileInferredSingleRoute(t *testing.T) { assertHttpRouteWeightChanges(t, httpRoutes[0], "", 10, 90) } } + +func TestHttpReconcileMirrorRoute(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) + client := testutil.NewFakeDynamicClient(obj) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + // Test for both the HTTP VS & Mixed VS + setMirror1 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + } + var percentage int32 = 90 + setMirror2 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-2", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + Percentage: &percentage, + } + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: "test-mirror-1", + }, { + Name: "test-mirror-2", + }, + }...) + + err := r.SetMirrorRoute(setMirror1) + assert.Nil(t, err) + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + // HTTP Routes + httpRoutes := extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 3) + + // Assertions + assert.Equal(t, httpRoutes[0].Name, "test-mirror-1") + checkDestination(t, httpRoutes[0].Route, "canary", 0) + assert.Equal(t, httpRoutes[0].Mirror.Host, "canary") + assert.Equal(t, httpRoutes[0].Mirror.Subset, "") + assert.Equal(t, httpRoutes[0].MirrorPercentage.Value, float64(100)) + assert.Equal(t, len(httpRoutes[0].Route), 2) + assert.Equal(t, httpRoutes[1].Name, "primary") + checkDestination(t, httpRoutes[1].Route, "stable", 100) + assert.Equal(t, httpRoutes[2].Name, "secondary") + checkDestination(t, httpRoutes[2].Route, "stable", 100) + + //Delete mirror route + deleteSetMirror := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + } + err = r.SetMirrorRoute(deleteSetMirror) + assert.Nil(t, err) + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 2) + assert.Equal(t, httpRoutes[0].Name, "primary") + assert.Equal(t, httpRoutes[1].Name, "secondary") + + //Test adding two routes using fake client then cleaning them up with RemoveManagedRoutes + err = r.SetMirrorRoute(setMirror1) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror2) + assert.Nil(t, err) + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 4) + assert.Equal(t, httpRoutes[1].MirrorPercentage.Value, float64(90)) + + r.RemoveManagedRoutes() + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 2) + +} + +func TestHttpReconcileMirrorRouteOrder(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary", "secondary"}) + obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) + client := testutil.NewFakeDynamicClient(obj) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + setMirror1 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + } + var percentage int32 = 90 + setMirror2 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-2", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "POST", + }, + }}, + Percentage: &percentage, + } + setMirror3 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-3", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + Percentage: &percentage, + } + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: "test-mirror-2", + }, { + Name: "test-mirror-3", + }, { + Name: "test-mirror-1", + }, + }...) + + err := r.SetMirrorRoute(setMirror1) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror2) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror3) + assert.Nil(t, err) + err = r.SetWeight(40) + assert.Nil(t, err) + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + // HTTP Routes + httpRoutes := extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 5) + assert.Equal(t, httpRoutes[0].Name, "test-mirror-2") + checkDestination(t, httpRoutes[0].Route, "canary", 40) + checkDestination(t, httpRoutes[0].Route, "stable", 60) + assert.Equal(t, httpRoutes[1].Name, "test-mirror-3") + assert.Equal(t, httpRoutes[2].Name, "test-mirror-1") + assert.Equal(t, httpRoutes[3].Name, "primary") + assert.Equal(t, httpRoutes[4].Name, "secondary") + + //Delete mirror route + deleteSetMirror := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-3", + } + err = r.SetMirrorRoute(deleteSetMirror) + assert.Nil(t, err) + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 4) + assert.Equal(t, httpRoutes[0].Name, "test-mirror-2") + assert.Equal(t, httpRoutes[1].Name, "test-mirror-1") + assert.Equal(t, httpRoutes[2].Name, "primary") + assert.Equal(t, httpRoutes[3].Name, "secondary") + + r.RemoveManagedRoutes() + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 2) +} + +func TestHttpReconcileMirrorRouteOrderSingleRouteNoName(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{}) + obj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteVsvc) + client := testutil.NewFakeDynamicClient(obj) + _, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), nil, druleLister) + client.ClearActions() + + setMirror1 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + } + var percentage int32 = 90 + setMirror2 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-2", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "POST", + }, + }}, + Percentage: &percentage, + } + setMirror3 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-3", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + Percentage: &percentage, + } + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: "test-mirror-2", + }, { + Name: "test-mirror-3", + }, { + Name: "test-mirror-1", + }, + }...) + + err := r.SetWeight(30) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror1) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror2) + assert.Nil(t, err) + err = r.SetMirrorRoute(setMirror3) + assert.Nil(t, err) + + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + // HTTP Routes + httpRoutes := extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 4) + assert.Equal(t, httpRoutes[0].Name, "test-mirror-2") + assert.Equal(t, httpRoutes[1].Name, "test-mirror-3") + assert.Equal(t, httpRoutes[2].Name, "test-mirror-1") + assert.Equal(t, httpRoutes[3].Name, "") + assert.Equal(t, httpRoutes[3].Route[0].Weight, int64(70)) + assert.Equal(t, httpRoutes[3].Route[1].Weight, int64(30)) + checkDestination(t, httpRoutes[0].Route, "canary", 30) + checkDestination(t, httpRoutes[1].Route, "stable", 70) + + err = r.SetWeight(40) + assert.Nil(t, err) + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + // HTTP Routes + httpRoutes = extractHttpRoutes(t, iVirtualService) + checkDestination(t, httpRoutes[0].Route, "canary", 40) + checkDestination(t, httpRoutes[1].Route, "stable", 60) + + //Delete mirror route + deleteSetMirror := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-3", + } + err = r.SetMirrorRoute(deleteSetMirror) + assert.Nil(t, err) + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 3) + assert.Equal(t, httpRoutes[0].Name, "test-mirror-2") + assert.Equal(t, httpRoutes[1].Name, "test-mirror-1") + assert.Equal(t, httpRoutes[2].Name, "") + + r.RemoveManagedRoutes() + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 1) +} + +func TestHttpReconcileMirrorRouteSubset(t *testing.T) { + + ro := rolloutWithDestinationRule() + const RolloutService = "rollout-service" + const StableSubsetName = "stable-subset" + const CanarySubsetName = "canary-subset" + ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name = "vsvc" + ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes = nil + ro.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.StableSubsetName = StableSubsetName + ro.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule.CanarySubsetName = CanarySubsetName + dRule := unstructuredutil.StrToUnstructuredUnsafe(` +apiVersion: networking.istio.io/v1alpha3 +kind: DestinationRule +metadata: + name: istio-destrule + namespace: default +spec: + host: rollout-service + subsets: + - name: stable-subset + - name: canary-subset +`) + + //ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + obj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteSubsetVsvc) + client := testutil.NewFakeDynamicClient(obj, dRule) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + // Test for both the HTTP VS & Mixed VS + setMirror1 := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + } + r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(r.rollout.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, []v1alpha1.MangedRoutes{{ + Name: "test-mirror-1", + }, + }...) + + err := r.SetMirrorRoute(setMirror1) + assert.Nil(t, err) + iVirtualService, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + // HTTP Routes + httpRoutes := extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 2) + + // Assertions + assert.Equal(t, httpRoutes[0].Name, "test-mirror-1") + assert.Equal(t, httpRoutes[0].Mirror.Host, RolloutService) + assert.Equal(t, httpRoutes[0].Mirror.Subset, CanarySubsetName) + assert.Equal(t, httpRoutes[0].Route[0].Destination.Host, RolloutService) + assert.Equal(t, httpRoutes[0].Route[0].Destination.Subset, StableSubsetName) + assert.Equal(t, httpRoutes[0].Route[1].Destination.Host, RolloutService) + assert.Equal(t, httpRoutes[0].Route[1].Destination.Subset, CanarySubsetName) + assert.Equal(t, len(httpRoutes[0].Route), 2) + + assert.Equal(t, httpRoutes[1].Name, "") + assert.Nil(t, httpRoutes[1].Mirror) + assert.Equal(t, httpRoutes[1].Route[0].Destination.Host, RolloutService) + assert.Equal(t, httpRoutes[1].Route[0].Destination.Subset, StableSubsetName) + assert.Equal(t, httpRoutes[1].Route[1].Destination.Host, RolloutService) + assert.Equal(t, httpRoutes[1].Route[1].Destination.Subset, CanarySubsetName) + assert.Equal(t, len(httpRoutes[1].Route), 2) + + r.RemoveManagedRoutes() + iVirtualService, err = client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(r.rollout.Namespace).Get(context.TODO(), ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name, metav1.GetOptions{}) + assert.NoError(t, err) + httpRoutes = extractHttpRoutes(t, iVirtualService) + assert.Equal(t, len(httpRoutes), 1) +} + +func TestReconcileUpdateMirror(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes = append(ro.Spec.Strategy.Canary.TrafficRouting.ManagedRoutes, v1alpha1.MangedRoutes{ + Name: "test-mirror-1", + }) + AssertReconcileUpdateMirror(t, regularVsvc, ro) +} +func AssertReconcileUpdateMirror(t *testing.T, vsvc string, ro *v1alpha1.Rollout) *dynamicfake.FakeDynamicClient { + obj := unstructuredutil.StrToUnstructuredUnsafe(vsvc) + client := testutil.NewFakeDynamicClient(obj) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + + setMirror := &v1alpha1.SetMirrorRoute{ + Name: "test-mirror-1", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{ + Exact: "GET", + }, + }}, + } + err := r.SetMirrorRoute(setMirror) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 1) + assert.Equal(t, "update", actions[0].GetVerb()) + return client +} diff --git a/rollout/trafficrouting/istio/istio_types.go b/rollout/trafficrouting/istio/istio_types.go index 4e14a60bdf..ce714757d2 100644 --- a/rollout/trafficrouting/istio/istio_types.go +++ b/rollout/trafficrouting/istio/istio_types.go @@ -1,6 +1,7 @@ package istio import ( + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -17,8 +18,27 @@ type VirtualServiceSpec struct { // VirtualServiceHTTPRoute is a HTTP route in a VirtualService type VirtualServiceHTTPRoute struct { - Name string `json:"name,omitempty"` - Route []VirtualServiceRouteDestination `json:"route,omitempty"` + Name string `json:"name,omitempty"` + Match []RouteMatch `json:"match,omitempty"` + Route []VirtualServiceRouteDestination `json:"route,omitempty"` + Mirror *VirtualServiceDestination `json:"mirror,omitempty"` + MirrorPercentage *Percent `json:"mirrorPercentage,omitempty"` +} + +type RouteMatch struct { + // Method What http methods should be mirrored + // +optional + Method *v1alpha1.StringMatch `json:"method,omitempty" protobuf:"bytes,1,opt,name=method"` + // Uri What url paths should be mirrored + // +optional + Uri *v1alpha1.StringMatch `json:"uri,omitempty" protobuf:"bytes,2,opt,name=uri"` + // Headers What request with matching headers should be mirrored + // +optional + Headers map[string]v1alpha1.StringMatch `json:"headers,omitempty" protobuf:"bytes,3,opt,name=headers"` +} + +type Percent struct { + Value float64 `json:"value,omitempty"` } // VirtualServiceTLSRoute is a TLS route in a VirtualService diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index b278a182ff..52a687a496 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -325,7 +325,7 @@ func (r *Reconciler) SetWeightPerIngress(desiredWeight int32, ingresses []string return nil } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } @@ -337,3 +337,11 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations .. func (r *Reconciler) UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error { return nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/nginx/nginx_test.go b/rollout/trafficrouting/nginx/nginx_test.go index d0037574c9..7aca48c9d5 100644 --- a/rollout/trafficrouting/nginx/nginx_test.go +++ b/rollout/trafficrouting/nginx/nginx_test.go @@ -1194,6 +1194,45 @@ func TestReconcileCanaryCreateErrorAlreadyExistsPatch(t *testing.T) { } } +func TestSetHeaderRoute(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRollout("stable-service", "canary-service", "stable-ingress"), + }, + } + err := r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) +} + +func TestSetMirrorRoute(t *testing.T) { + r := Reconciler{ + cfg: ReconcilerConfig{ + Rollout: fakeRollout("stable-service", "canary-service", "stable-ingress"), + }, + } + err := r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) +} + func TestReconcileCanaryCreateErrorAlreadyExistsPatchMultiIngress(t *testing.T) { rollout := fakeRolloutWithMultiIngress("stable-service", "canary-service", "stable-ingress", "additional-stable-ingress") stableIngress := extensionsIngress("stable-ingress", 80, "stable-service") diff --git a/rollout/trafficrouting/smi/smi.go b/rollout/trafficrouting/smi/smi.go index 8be3584abf..c781799f87 100644 --- a/rollout/trafficrouting/smi/smi.go +++ b/rollout/trafficrouting/smi/smi.go @@ -218,7 +218,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...v1 return r.patchTrafficSplit(existingTrafficSplit, trafficSplits) } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } @@ -351,3 +351,11 @@ func trafficSplitV1Alpha3(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, ro func (r *Reconciler) UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error { return nil } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/smi/smi_test.go b/rollout/trafficrouting/smi/smi_test.go index 22a69d6098..136e006a14 100644 --- a/rollout/trafficrouting/smi/smi_test.go +++ b/rollout/trafficrouting/smi/smi_test.go @@ -576,3 +576,62 @@ func TestCreateTrafficSplitForMultipleBackends(t *testing.T) { assert.Equal(t, 80, ts3.Spec.Backends[3].Weight) }) } + +func TestReconcileSetHeaderRoute(t *testing.T) { + t.Run("not implemented", func(t *testing.T) { + ro := fakeRollout("stable-service", "canary-service", "", "") + client := fake.NewSimpleClientset() + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{}, + }) + assert.Nil(t, err) + + err = r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 0) + }) +} + +func TestReconcileSetMirrorRoute(t *testing.T) { + t.Run("not implemented", func(t *testing.T) { + ro := fakeRollout("stable-service", "canary-service", "", "") + client := fake.NewSimpleClientset() + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{}, + }) + assert.Nil(t, err) + + err = r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + assert.Nil(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 0) + }) +} diff --git a/rollout/trafficrouting/traefik/traefik.go b/rollout/trafficrouting/traefik/traefik.go index 22c3f56875..f5b507207f 100644 --- a/rollout/trafficrouting/traefik/traefik.go +++ b/rollout/trafficrouting/traefik/traefik.go @@ -156,7 +156,7 @@ func getService(serviceName string, services []interface{}) (map[string]interfac return selectedService, nil } -func (r *Reconciler) SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error { +func (r *Reconciler) SetHeaderRoute(headerRouting *v1alpha1.SetHeaderRoute) error { return nil } @@ -167,3 +167,11 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations .. func (r *Reconciler) Type() string { return Type } + +func (r *Reconciler) SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error { + return nil +} + +func (r *Reconciler) RemoveManagedRoutes() error { + return nil +} diff --git a/rollout/trafficrouting/traefik/traefik_test.go b/rollout/trafficrouting/traefik/traefik_test.go index e3c0b5e774..d7dbbbd297 100644 --- a/rollout/trafficrouting/traefik/traefik_test.go +++ b/rollout/trafficrouting/traefik/traefik_test.go @@ -196,6 +196,61 @@ func TestSetWeight(t *testing.T) { }) } +func TestSetHeaderRoute(t *testing.T) { + t.Run("SetHeaderRoute", func(t *testing.T) { + // Given + t.Parallel() + cfg := ReconcilerConfig{ + Rollout: newRollout(stableServiceName, canaryServiceName, traefikServiceName), + Client: client, + } + r := NewReconciler(&cfg) + + // When + err := r.SetHeaderRoute(&v1alpha1.SetHeaderRoute{ + Name: "set-header", + Match: []v1alpha1.HeaderRoutingMatch{{ + HeaderName: "header-name", + HeaderValue: &v1alpha1.StringMatch{ + Exact: "value", + }, + }}, + }) + + // Then + assert.NoError(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + }) +} + +func TestSetMirrorRoute(t *testing.T) { + t.Run("SetMirrorRoute", func(t *testing.T) { + // Given + t.Parallel() + cfg := ReconcilerConfig{ + Rollout: newRollout(stableServiceName, canaryServiceName, traefikServiceName), + Client: client, + } + r := NewReconciler(&cfg) + + // When + err := r.SetMirrorRoute(&v1alpha1.SetMirrorRoute{ + Name: "mirror-route", + Match: []v1alpha1.RouteMatch{{ + Method: &v1alpha1.StringMatch{Exact: "GET"}, + }}, + }) + + // Then + assert.NoError(t, err) + + err = r.RemoveManagedRoutes() + assert.Nil(t, err) + }) +} + func toUnstructured(t *testing.T, manifest string) *unstructured.Unstructured { t.Helper() obj := &unstructured.Unstructured{} diff --git a/rollout/trafficrouting/trafficroutingutil.go b/rollout/trafficrouting/trafficroutingutil.go index 08569c4a16..3ff9c9c4ec 100644 --- a/rollout/trafficrouting/trafficroutingutil.go +++ b/rollout/trafficrouting/trafficroutingutil.go @@ -10,11 +10,15 @@ type TrafficRoutingReconciler interface { UpdateHash(canaryHash, stableHash string, additionalDestinations ...v1alpha1.WeightDestination) error // SetWeight sets the canary weight to the desired weight SetWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) error - // SetHeaderRouting sets the header routing step - SetHeaderRouting(headerRouting *v1alpha1.SetHeaderRouting) error + // SetHeaderRoute sets the header routing step + SetHeaderRoute(setHeaderRoute *v1alpha1.SetHeaderRoute) error + // SetMirrorRoute sets up the traffic router to mirror traffic to a service + SetMirrorRoute(setMirrorRoute *v1alpha1.SetMirrorRoute) error // VerifyWeight returns true if the canary is at the desired weight and additionalDestinations are at the weights specified // Returns nil if weight verification is not supported or not applicable VerifyWeight(desiredWeight int32, additionalDestinations ...v1alpha1.WeightDestination) (*bool, error) + // RemoveAllRoutes Removes all routes that are managed by rollouts by looking at spec.strategy.canary.trafficRouting.managedRoutes + RemoveManagedRoutes() error // Type returns the type of the traffic routing reconciler Type() string } diff --git a/rollout/trafficrouting_test.go b/rollout/trafficrouting_test.go index 11941a9aea..6c1e49f476 100644 --- a/rollout/trafficrouting_test.go +++ b/rollout/trafficrouting_test.go @@ -35,9 +35,11 @@ func newFakeSingleTrafficRoutingReconciler() *mocks.TrafficRoutingReconciler { trafficRoutingReconciler := mocks.TrafficRoutingReconciler{} trafficRoutingReconciler.On("Type").Return("fake") trafficRoutingReconciler.On("SetWeight", mock.Anything, mock.Anything).Return(nil) - trafficRoutingReconciler.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + trafficRoutingReconciler.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + trafficRoutingReconciler.On("SetMirrorRoute", mock.Anything, mock.Anything).Return(nil) trafficRoutingReconciler.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) trafficRoutingReconciler.On("UpdateHash", mock.Anything, mock.Anything, mock.Anything).Return(nil) + trafficRoutingReconciler.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) return &trafficRoutingReconciler } @@ -99,7 +101,7 @@ func TestReconcileTrafficRoutingVerifyWeightErr(t *testing.T) { f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(false), errors.New("Error message")) f.expectPatchRolloutAction(ro) f.run(getKey(ro, t)) @@ -112,7 +114,7 @@ func TestReconcileTrafficRoutingVerifyWeightFalse(t *testing.T) { f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(false), nil) c, i, k8sI := f.newController(noResyncPeriodFunc) enqueued := false @@ -174,8 +176,8 @@ func TestRolloutUseDesiredWeight(t *testing.T) { assert.Equal(t, int32(10), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(true, nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r2, t)) } @@ -223,8 +225,8 @@ func TestRolloutUseDesiredWeight100(t *testing.T) { assert.Equal(t, int32(100), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(true, nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r2, t)) } @@ -291,7 +293,7 @@ func TestRolloutWithExperimentStep(t *testing.T) { assert.Equal(t, ex.Status.TemplateStatuses[0].PodTemplateHash, weightDestinations[0].PodTemplateHash) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(func(desiredWeight int32, weightDestinations ...v1alpha1.WeightDestination) error { assert.Equal(t, int32(10), desiredWeight) assert.Equal(t, int32(5), weightDestinations[0].Weight) @@ -312,7 +314,7 @@ func TestRolloutWithExperimentStep(t *testing.T) { assert.Len(t, weightDestinations, 0) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(func(desiredWeight int32, weightDestinations ...v1alpha1.WeightDestination) error { assert.Equal(t, int32(10), desiredWeight) assert.Len(t, weightDestinations, 0) @@ -367,7 +369,7 @@ func TestRolloutUsePreviousSetWeight(t *testing.T) { assert.Equal(t, int32(10), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything, mock.Anything).Return(pointer.BoolPtr(true), nil) f.fakeTrafficRouting.On("error patching alb ingress", mock.Anything, mock.Anything).Return(true, nil) f.run(getKey(r2, t)) @@ -433,7 +435,8 @@ func TestRolloutUseDynamicWeightOnPromoteFull(t *testing.T) { assert.Equal(t, int32(50), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r2, t)) }) @@ -446,7 +449,8 @@ func TestRolloutUseDynamicWeightOnPromoteFull(t *testing.T) { assert.Equal(t, int32(5), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r2, t)) }) @@ -490,7 +494,8 @@ func TestRolloutSetWeightToZeroWhenFullyRolledOut(t *testing.T) { assert.Equal(t, int32(0), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r1, t)) } @@ -718,7 +723,9 @@ func TestCanaryWithTrafficRoutingAddScaleDownDelay(t *testing.T) { f := newFixture(t) defer f.Close() - r1 := newCanaryRollout("foo", 1, nil, nil, pointer.Int32Ptr(1), intstr.FromInt(1), intstr.FromInt(1)) + r1 := newCanaryRollout("foo", 1, nil, []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }}, pointer.Int32Ptr(0), intstr.FromInt(1), intstr.FromInt(1)) r1.Spec.Strategy.Canary.CanaryService = "canary" r1.Spec.Strategy.Canary.StableService = "stable" r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ @@ -730,9 +737,10 @@ func TestCanaryWithTrafficRoutingAddScaleDownDelay(t *testing.T) { rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] r2 = updateCanaryRolloutStatus(r2, rs2PodHash, 2, 1, 2, false) r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) - r2.Status.CurrentStepIndex = nil availableCondition, _ := newAvailableCondition(true) conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) _, r2.Status.Canary.Weights = calculateWeightStatus(r2, rs2PodHash, rs2PodHash, 0) selector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} @@ -758,7 +766,9 @@ func TestCanaryWithTrafficRoutingScaleDownLimit(t *testing.T) { inTheFuture := timeutil.MetaNow().Add(10 * time.Second).UTC().Format(time.RFC3339) - r1 := newCanaryRollout("foo", 1, nil, nil, pointer.Int32Ptr(1), intstr.FromInt(1), intstr.FromInt(1)) + r1 := newCanaryRollout("foo", 1, nil, []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(10), + }}, pointer.Int32Ptr(1), intstr.FromInt(1), intstr.FromInt(1)) rs1 := newReplicaSetWithStatus(r1, 1, 1) rs1.Annotations[v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey] = inTheFuture r1.Spec.Strategy.Canary.ScaleDownDelayRevisionLimit = pointer.Int32Ptr(1) @@ -863,7 +873,8 @@ func TestDynamicScalingDontIncreaseWeightWhenAborted(t *testing.T) { assert.Equal(t, int32(0), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r1, t)) } @@ -933,7 +944,8 @@ func TestDynamicScalingDecreaseWeightAccordingToStableAvailabilityWhenAborted(t assert.Equal(t, int32(80), desiredWeight) return nil }) - f.fakeTrafficRouting.On("SetHeaderRouting", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetHeaderRoute", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("RemoveManagedRoutes", mock.Anything, mock.Anything).Return(nil) f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(pointer.BoolPtr(true), nil) f.run(getKey(r1, t)) } diff --git a/server/server.go b/server/server.go index a7bb7f298e..f90b372d1c 100644 --- a/server/server.go +++ b/server/server.go @@ -1,13 +1,13 @@ package server import ( - "bufio" "context" "embed" "fmt" + "io/fs" "net" "net/http" - "os" + "path" "regexp" "strings" "time" @@ -81,45 +81,11 @@ func NewServer(o ServerOptions) *ArgoRolloutsServer { return &ArgoRolloutsServer{Options: o} } -type spaFileSystem struct { - root http.FileSystem -} - -func (fs *spaFileSystem) Open(name string) (http.File, error) { - f, err := fs.root.Open(name) - if os.IsNotExist(err) { - return fs.root.Open("index.html") - } - return f, err -} +var re = regexp.MustCompile(``) -//This function helps in changing base href to point to rootpath as basepath, we are making modification in only server/static/index.html file -func withRootPath(rootpath string) { - inputFile, inputError := os.Open("./server/static/index.html") - if inputError != nil { - log.Error("An error occurred on opening the inputfile\n" + - "Does the file exist?\n" + - "Have you got access to it?\n") - panic(inputError) // exit on error - } - defer inputFile.Close() - inputReader := bufio.NewReader(inputFile) - inputString, _ := inputReader.ReadString('\n') - re := regexp.MustCompile(``) - var temp = re.ReplaceAllString(inputString, "") // href="/root/" - - outputFile, _ := os.OpenFile("./server/static/index.html", os.O_TRUNC|os.O_WRONLY, 0666) - defer outputFile.Close() - outputWriter := bufio.NewWriter(outputFile) - outputWriter.WriteString(temp) - outputWriter.Flush() - - //To make ui/dist/index.html file consistent with server/static/index.html - outputFileDist, _ := os.OpenFile("./ui/dist/app/index.html", os.O_TRUNC|os.O_WRONLY, 0666) - defer outputFileDist.Close() - outputWriterDist := bufio.NewWriter(outputFileDist) - outputWriterDist.WriteString(temp) - outputWriterDist.Flush() +func withRootPath(fileContent []byte, rootpath string) []byte { + var temp = re.ReplaceAllString(string(fileContent), ``) + return []byte(temp) } func (s *ArgoRolloutsServer) newHTTPServer(ctx context.Context, port int) *http.Server { @@ -153,15 +119,101 @@ func (s *ArgoRolloutsServer) newHTTPServer(ctx context.Context, port int) *http. var handler http.Handler = gwmux - withRootPath(s.Options.RootPath) - mux.Handle("/api/", handler) - mux.Handle("/"+s.Options.RootPath+"/", http.StripPrefix("/"+s.Options.RootPath+"/", http.FileServer(http.Dir("./server/static")))) + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + requestedURI := path.Clean(r.RequestURI) + rootPath := path.Clean("/" + s.Options.RootPath) + //If the rootPath is not in the prefix 404 + if !strings.HasPrefix(requestedURI, rootPath) { + http.NotFound(w, r) + return + } + //If the rootPath is the requestedURI, serve index.html + if requestedURI == rootPath { + fileBytes, openErr := s.readIndexHtml() + if openErr != nil { + log.Errorf("Error opening file index.html: %v", openErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Write(fileBytes) + return + } + + embedPath := path.Join("static", strings.TrimPrefix(requestedURI, rootPath)) + file, openErr := static.Open(embedPath) + if openErr != nil { + fErr := openErr.(*fs.PathError) + //If the file is not found, serve index.html + if fErr.Err == fs.ErrNotExist { + fileBytes, openErr := s.readIndexHtml() + if openErr != nil { + log.Errorf("Error opening file index.html: %v", openErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Write(fileBytes) + return + } else { + log.Errorf("Error opening file %s: %v", embedPath, openErr) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + defer file.Close() + + stat, statErr := file.Stat() + if statErr != nil { + log.Errorf("Failed to stat file or dir %s: %v", embedPath, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + fileBytes := make([]byte, stat.Size()) + _, err = file.Read(fileBytes) + if err != nil { + log.Errorf("Failed to read file %s: %v", embedPath, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Write(fileBytes) + }) return &httpS } +func (s *ArgoRolloutsServer) readIndexHtml() ([]byte, error) { + file, err := static.Open("static/index.html") + if err != nil { + log.Errorf("Failed to open file %s: %v", "static/index.html", err) + return nil, err + } + defer func() { + if file != nil { + if err := file.Close(); err != nil { + log.Errorf("Error closing file: %v", err) + } + } + }() + + stat, err := file.Stat() + if err != nil { + log.Errorf("Failed to stat file or dir %s: %v", "static/index.html", err) + return nil, err + } + + fileBytes := make([]byte, stat.Size()) + _, err = file.Read(fileBytes) + if err != nil { + log.Errorf("Failed to read file %s: %v", "static/index.html", err) + return nil, err + } + + return withRootPath(fileBytes, s.Options.RootPath), nil +} + func (s *ArgoRolloutsServer) newGRPCServer() *grpc.Server { grpcS := grpc.NewServer() var rolloutsServer rollout.RolloutServiceServer = NewServer(s.Options) @@ -201,7 +253,7 @@ func (s *ArgoRolloutsServer) Run(ctx context.Context, port int, dashboard bool) startupMessage := fmt.Sprintf("Argo Rollouts api-server serving on port %d (namespace: %s)", port, s.Options.Namespace) if dashboard { - startupMessage = fmt.Sprintf("Argo Rollouts Dashboard is now available at localhost %d", port) + startupMessage = fmt.Sprintf("Argo Rollouts Dashboard is now available at http://localhost:%d/%s", port, s.Options.RootPath) } log.Info(startupMessage) diff --git a/test/e2e/analysis_test.go b/test/e2e/analysis_test.go index c5ee4f8e55..13b6d19b60 100644 --- a/test/e2e/analysis_test.go +++ b/test/e2e/analysis_test.go @@ -1,3 +1,4 @@ +//go:build e2e // +build e2e package e2e diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index f4380c3562..8ca5a026ac 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -90,10 +90,12 @@ spec: ExpectRevisionPodCount("1", 0). ExpectRevisionPodCount("2", 1). ExpectRolloutEvents([]string{ + "RolloutNotCompleted", // Rollout not completed, started update to revision 0 (7fd9b5545c) "RolloutUpdated", // Rollout updated to revision 1 "NewReplicaSetCreated", // Created ReplicaSet abort-retry-promote-698fbfb9dc (revision 1) "ScalingReplicaSet", // Scaled up ReplicaSet abort-retry-promote-698fbfb9dc (revision 1) from 0 to 1 "RolloutCompleted", // Rollout completed update to revision 1 (698fbfb9dc): Initial deploy + "RolloutNotCompleted", "RolloutUpdated", // Rollout updated to revision 2 "NewReplicaSetCreated", // Created ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) "ScalingReplicaSet", // Scaled up ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) from 0 to 1 @@ -706,6 +708,7 @@ func (s *FunctionalSuite) TestBlueGreenUpdate() { "SwitchService", // Switched selector for service 'bluegreen' from '' to '7dcd8f8869' "RolloutUpdated", // Rollout updated to revision 2 "NewReplicaSetCreated", // Created ReplicaSet bluegreen-5498785cd6 (revision 2) + "RolloutNotCompleted", // Rollout went to not completed state started update to revision 2 (85c6899) "ScalingReplicaSet", // Scaled up ReplicaSet bluegreen-5498785cd6 (revision 2) from 0 to 3 "SwitchService", // Switched selector for service 'bluegreen' from '7dcd8f8869' to '6c779b88b6' "RolloutCompleted", // Rollout completed update to revision 2 (6c779b88b6): Completed blue-green update @@ -959,7 +962,7 @@ spec: Then(). ExpectRevisionPodCount("2", 0). ExpectRollout("Abort=True", func(r *v1alpha1.Rollout) bool { - return r.Status.Abort == true && len(r.Status.Conditions) == 3 + return r.Status.Abort == true && len(r.Status.Conditions) == 4 }) } @@ -1120,10 +1123,10 @@ func (s *FunctionalSuite) TestKubectlWaitForCompleted() { kind: Service apiVersion: v1 metadata: - name: kubectl-wait-completed + name: kubectl-wait-healthy spec: selector: - app: kubectl-wait-completed + app: kubectl-wait-healthy ports: - protocol: TCP port: 80 @@ -1132,19 +1135,19 @@ spec: apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: - name: kubectl-wait-completed + name: kubectl-wait-healthy spec: replicas: 1 selector: matchLabels: - app: kubectl-wait-completed + app: kubectl-wait-healthy template: metadata: labels: - app: kubectl-wait-completed + app: kubectl-wait-healthy spec: containers: - - name: kubectl-wait-completed + - name: kubectl-wait-healthy image: nginx:1.19-alpine imagePullPolicy: Always ports: @@ -1158,21 +1161,21 @@ spec: strategy: blueGreen: - activeService: kubectl-wait-completed + activeService: kubectl-wait-healthy autoPromotionEnabled: true `). When(). UpdateSpec(). Then(). - ExpectRollout("Completed=False", func(r *v1alpha1.Rollout) bool { - cmd := exec.Command("kubectl", "wait", "--for=condition=Completed=False", fmt.Sprintf("rollout/%s", r.Name)) + ExpectRollout("Healthy=False", func(r *v1alpha1.Rollout) bool { + cmd := exec.Command("kubectl", "wait", "--for=condition=Healthy=False", fmt.Sprintf("rollout/%s", r.Name)) out, err := cmd.CombinedOutput() return err == nil && strings.Contains(string(out), fmt.Sprintf("rollout.argoproj.io/%s condition met", r.Name)) }). ExpectRolloutStatus("Progressing"). ExpectActiveRevision("1"). - ExpectRollout("Completed=True", func(r *v1alpha1.Rollout) bool { - cmd := exec.Command("kubectl", "wait", "--for=condition=Completed=True", fmt.Sprintf("rollout/%s", r.Name)) + ExpectRollout("Healthy=True", func(r *v1alpha1.Rollout) bool { + cmd := exec.Command("kubectl", "wait", "--for=condition=Healthy=True", fmt.Sprintf("rollout/%s", r.Name)) out, err := cmd.CombinedOutput() return err == nil && strings.Contains(string(out), fmt.Sprintf("rollout.argoproj.io/%s condition met", r.Name)) }). diff --git a/test/e2e/header-routing/istio-hr-host.yaml b/test/e2e/header-routing/istio-hr-host.yaml index 6c3fa050bb..8b1036fb42 100644 --- a/test/e2e/header-routing/istio-hr-host.yaml +++ b/test/e2e/header-routing/istio-hr-host.yaml @@ -76,6 +76,8 @@ spec: canaryService: canary-service stableService: stable-service trafficRouting: + managedRoutes: + - name: set-header-1 istio: virtualService: name: rollouts-demo-vsvc @@ -83,20 +85,23 @@ spec: - primary steps: - setWeight: 20 - - setHeaderRouting: + - setHeaderRoute: + name: set-header-1 match: - headerName: agent headerValue: regex: firefox(.*) - pause: { } - - setHeaderRouting: + - setHeaderRoute: + name: set-header-1 match: - headerName: agent headerValue: regex: chrome(.*) - pause: { } - setWeight: 40 - - setHeaderRouting: {} + - setHeaderRoute: + name: set-header-1 - pause: {} --- apiVersion: networking.istio.io/v1alpha3 diff --git a/test/e2e/header_routing_test.go b/test/e2e/header_route_test.go similarity index 85% rename from test/e2e/header_routing_test.go rename to test/e2e/header_route_test.go index a1aab16999..5b9fe7cd16 100644 --- a/test/e2e/header_routing_test.go +++ b/test/e2e/header_route_test.go @@ -15,22 +15,22 @@ import ( "github.com/argoproj/argo-rollouts/test/fixtures" ) -type HeaderRoutingSuite struct { +type HeaderRouteSuite struct { fixtures.E2ESuite } func TestHeaderRoutingSuite(t *testing.T) { - suite.Run(t, new(HeaderRoutingSuite)) + suite.Run(t, new(HeaderRouteSuite)) } -func (s *HeaderRoutingSuite) SetupSuite() { +func (s *HeaderRouteSuite) SetupSuite() { s.E2ESuite.SetupSuite() if !s.IstioEnabled { s.T().SkipNow() } } -func (s *HeaderRoutingSuite) TestIstioHostHeaderRoute() { +func (s *HeaderRouteSuite) TestIstioHostHeaderRoute() { s.Given(). RolloutObjects("@header-routing/istio-hr-host.yaml"). When(). @@ -48,7 +48,7 @@ func (s *HeaderRoutingSuite) TestIstioHostHeaderRoute() { Then(). Assert(func(t *fixtures.Then) { vsvc := t.GetVirtualService() - assert.Equal(s.T(), istio.HeaderRouteName, vsvc.Spec.HTTP[0].Name) + assert.Equal(s.T(), "set-header-1", vsvc.Spec.HTTP[0].Name) assertDestination(s, vsvc.Spec.HTTP[0], "canary-service", int64(100)) assertDestination(s, vsvc.Spec.HTTP[1], "stable-service", int64(80)) assertDestination(s, vsvc.Spec.HTTP[1], "canary-service", int64(20)) @@ -80,7 +80,7 @@ func (s *HeaderRoutingSuite) TestIstioHostHeaderRoute() { }) } -func assertDestination(s *HeaderRoutingSuite, route istio.VirtualServiceHTTPRoute, service string, weight int64) { +func assertDestination(s *HeaderRouteSuite, route istio.VirtualServiceHTTPRoute, service string, weight int64) { for _, destination := range route.Route { if destination.Destination.Host == service { assert.Equal(s.T(), weight, destination.Weight) diff --git a/test/e2e/mirror-route/istio-mirror-host.yaml b/test/e2e/mirror-route/istio-mirror-host.yaml new file mode 100644 index 0000000000..d3f20b0352 --- /dev/null +++ b/test/e2e/mirror-route/istio-mirror-host.yaml @@ -0,0 +1,124 @@ +apiVersion: v1 +kind: Service +metadata: + name: canary-service +spec: + selector: + app: rollouts-demo + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http +--- +apiVersion: v1 +kind: Service +metadata: + name: stable-service +spec: + selector: + app: rollouts-demo + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: rollouts-demo-vsvc +spec: + gateways: + - rollouts-demo-gateway + hosts: + - rollouts-demo.com + http: + - name: primary + route: + - destination: + host: stable-service + weight: 80 + - destination: + host: canary-service + weight: 20 + +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollouts-demo +spec: + replicas: 5 + selector: + matchLabels: + app: rollouts-demo + template: + metadata: + labels: + app: rollouts-demo + spec: + containers: + - name: rollouts-demo + image: "nginx:1.19-alpine" + ports: + - name: http + containerPort: 8080 + protocol: TCP + strategy: + canary: + canaryService: canary-service + stableService: stable-service + trafficRouting: + managedRoutes: + - name: mirror-route-1 + - name: mirror-route-2 + istio: + virtualService: + name: rollouts-demo-vsvc + routes: + - primary + steps: + - setWeight: 20 + - setMirrorRoute: + name: mirror-route-1 + percentage: 100 + match: + - path: + prefix: / + - setMirrorRoute: + name: mirror-route-2 + percentage: 80 + match: + - path: + prefix: / + method: + exact: GET + - pause: { } + - setMirrorRoute: + name: mirror-route-1 + percentage: 100 + match: + - path: + prefix: /rewrite + - pause: { } + - setWeight: 40 + - setMirrorRoute: + name: mirror-route-1 + - pause: {} +--- +apiVersion: networking.istio.io/v1alpha3 +kind: Gateway +metadata: + name: rollouts-demo-gateway +spec: + selector: + istio: ingressgateway + servers: + - hosts: + - '*' + port: + name: http + number: 80 + protocol: HTTP diff --git a/test/e2e/mirror_route_test.go b/test/e2e/mirror_route_test.go new file mode 100644 index 0000000000..5190a985f0 --- /dev/null +++ b/test/e2e/mirror_route_test.go @@ -0,0 +1,102 @@ +//go:build e2e +// +build e2e + +package e2e + +import ( + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/istio" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/tj/assert" + + "github.com/argoproj/argo-rollouts/test/fixtures" +) + +type MirrorRouteSuite struct { + fixtures.E2ESuite +} + +func TestMirrorRouteSuite(t *testing.T) { + suite.Run(t, new(MirrorRouteSuite)) +} + +func (s *MirrorRouteSuite) SetupSuite() { + s.E2ESuite.SetupSuite() + if !s.IstioEnabled { + s.T().SkipNow() + } +} + +func (s *MirrorRouteSuite) TestIstioHostMirrorRoute() { + s.Given(). + RolloutObjects("@mirror-route/istio-mirror-host.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), "primary", vsvc.Spec.HTTP[0].Name) + }). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Sleep(1 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), "mirror-route-1", vsvc.Spec.HTTP[0].Name) + assert.Equal(s.T(), float64(100), vsvc.Spec.HTTP[0].MirrorPercentage.Value) + assert.Equal(s.T(), "mirror-route-2", vsvc.Spec.HTTP[1].Name) + assert.Equal(s.T(), float64(80), vsvc.Spec.HTTP[1].MirrorPercentage.Value) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "stable-service", int64(80)) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "canary-service", int64(20)) + assertMirrorDestination(s, vsvc.Spec.HTTP[1], "stable-service", int64(80)) + assertMirrorDestination(s, vsvc.Spec.HTTP[1], "canary-service", int64(20)) + + assertMirrorDestination(s, vsvc.Spec.HTTP[2], "stable-service", int64(80)) + assertMirrorDestination(s, vsvc.Spec.HTTP[2], "canary-service", int64(20)) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Paused"). + Sleep(1 * time.Second). + Then(). + When(). + PromoteRollout(). + WaitForRolloutStatus("Paused"). + Sleep(1 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), "mirror-route-2", vsvc.Spec.HTTP[0].Name) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "stable-service", int64(60)) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "canary-service", int64(40)) + assert.Equal(s.T(), "primary", vsvc.Spec.HTTP[1].Name) + assertMirrorDestination(s, vsvc.Spec.HTTP[1], "stable-service", int64(60)) + assertMirrorDestination(s, vsvc.Spec.HTTP[1], "canary-service", int64(40)) + }). + When(). + PromoteRolloutFull(). + WaitForRolloutStatus("Healthy"). + Sleep(1 * time.Second). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), 1, len(vsvc.Spec.HTTP)) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "stable-service", int64(100)) + assertMirrorDestination(s, vsvc.Spec.HTTP[0], "canary-service", int64(0)) + }) +} + +func assertMirrorDestination(s *MirrorRouteSuite, route istio.VirtualServiceHTTPRoute, service string, weight int64) { + for _, destination := range route.Route { + if destination.Destination.Host == service { + assert.Equal(s.T(), weight, destination.Weight) + return + } + } + assert.Fail(s.T(), "Could not find the destination for service: %s", service) +} diff --git a/test/fixtures/e2e_suite.go b/test/fixtures/e2e_suite.go index fdac7fcd3f..6eb059b446 100644 --- a/test/fixtures/e2e_suite.go +++ b/test/fixtures/e2e_suite.go @@ -52,7 +52,7 @@ const ( ) var ( - E2EWaitTimeout time.Duration = time.Second * 90 + E2EWaitTimeout time.Duration = time.Second * 120 E2EPodDelay = 0 E2EALBIngressAnnotations map[string]string @@ -140,8 +140,8 @@ func (s *E2ESuite) SetupSuite() { restConfig, err := config.ClientConfig() s.CheckError(err) s.Common.kubernetesHost = restConfig.Host - restConfig.Burst = defaults.DefaultBurst - restConfig.QPS = defaults.DefaultQPS + restConfig.Burst = defaults.DefaultBurst * 2 + restConfig.QPS = defaults.DefaultQPS * 2 s.namespace, _, err = config.Namespace() s.CheckError(err) s.kubeClient, err = kubernetes.NewForConfig(restConfig) diff --git a/test/fixtures/when.go b/test/fixtures/when.go index ea6d855e43..40998dc73f 100644 --- a/test/fixtures/when.go +++ b/test/fixtures/when.go @@ -452,10 +452,17 @@ func (w *When) DeleteRollout() *When { func (w *When) WaitForExperimentCondition(name string, test func(ex *rov1.Experiment) bool, condition string, timeout time.Duration) *When { start := time.Now() w.log.Infof("Waiting for Experiment %s condition: %s", name, condition) - opts := metav1.ListOptions{FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name)).String()} - watch, err := w.rolloutClient.ArgoprojV1alpha1().Experiments(w.namespace).Watch(w.Context, opts) + exIf := w.dynamicClient.Resource(rov1.ExperimentGVR).Namespace(w.namespace) + ex, err := exIf.Get(w.Context, name, metav1.GetOptions{}) w.CheckError(err) - defer watch.Stop() + retryWatcher, err := watchutil.NewRetryWatcher(ex.GetResourceVersion(), &cache.ListWatch{ + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + opts := metav1.ListOptions{FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name)).String()} + return w.rolloutClient.ArgoprojV1alpha1().Experiments(w.namespace).Watch(w.Context, opts) + }, + }) + w.CheckError(err) + defer retryWatcher.Stop() timeoutCh := make(chan bool, 1) go func() { time.Sleep(timeout) @@ -463,7 +470,7 @@ func (w *When) WaitForExperimentCondition(name string, test func(ex *rov1.Experi }() for { select { - case event := <-watch.ResultChan(): + case event := <-retryWatcher.ResultChan(): ex, ok := event.Object.(*rov1.Experiment) if ok { if test(ex) { @@ -482,10 +489,17 @@ func (w *When) WaitForExperimentCondition(name string, test func(ex *rov1.Experi func (w *When) WaitForAnalysisRunCondition(name string, test func(ar *rov1.AnalysisRun) bool, condition string, timeout time.Duration) *When { start := time.Now() w.log.Infof("Waiting for AnalysisRun %s condition: %s", name, condition) - opts := metav1.ListOptions{FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name)).String()} - watch, err := w.rolloutClient.ArgoprojV1alpha1().AnalysisRuns(w.namespace).Watch(w.Context, opts) + arIf := w.dynamicClient.Resource(rov1.AnalysisRunGVR).Namespace(w.namespace) + ar, err := arIf.Get(w.Context, name, metav1.GetOptions{}) w.CheckError(err) - defer watch.Stop() + retryWatcher, err := watchutil.NewRetryWatcher(ar.GetResourceVersion(), &cache.ListWatch{ + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + opts := metav1.ListOptions{FieldSelector: fields.ParseSelectorOrDie(fmt.Sprintf("metadata.name=%s", name)).String()} + return w.rolloutClient.ArgoprojV1alpha1().AnalysisRuns(w.namespace).Watch(w.Context, opts) + }, + }) + w.CheckError(err) + defer retryWatcher.Stop() timeoutCh := make(chan bool, 1) go func() { time.Sleep(timeout) @@ -493,7 +507,7 @@ func (w *When) WaitForAnalysisRunCondition(name string, test func(ar *rov1.Analy }() for { select { - case event := <-watch.ResultChan(): + case event := <-retryWatcher.ResultChan(): ar, ok := event.Object.(*rov1.AnalysisRun) if ok { if test(ar) { diff --git a/ui/package.json b/ui/package.json index 898f96cf6b..15627c8da8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -5,6 +5,7 @@ "dependencies": { "argo-ui": "git+https://github.com/argoproj/argo-ui.git", "classnames": "2.2.6", + "isomorphic-fetch": "^3.0.0", "moment": "^2.29.1", "moment-timezone": "^0.5.33", "portable-fetch": "^3.0.0", diff --git a/ui/src/app/App.tsx b/ui/src/app/App.tsx index 644aa3b4e7..b1d28fa5d5 100644 --- a/ui/src/app/App.tsx +++ b/ui/src/app/App.tsx @@ -92,7 +92,7 @@ const App = () => { } shortcuts={[ {key: '/', description: 'Search'}, @@ -102,7 +102,7 @@ const App = () => { ]} changeNamespace={changeNamespace} /> - } changeNamespace={changeNamespace} /> + } changeNamespace={changeNamespace} /> @@ -112,4 +112,4 @@ const App = () => { ); }; -export default App; \ No newline at end of file +export default App; diff --git a/ui/src/app/components/header/header.tsx b/ui/src/app/components/header/header.tsx index 90d51ee7c8..edda274ea8 100644 --- a/ui/src/app/components/header/header.tsx +++ b/ui/src/app/components/header/header.tsx @@ -13,6 +13,7 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: const history = useHistory(); const namespaceInfo = React.useContext(NamespaceContext); const {name} = useParams<{name: string}>(); + const {namespace} = useParams<{namespace: string}>(); const api = React.useContext(RolloutAPIContext); const [version, setVersion] = React.useState('v?'); const [nsInput, setNsInput] = React.useState(namespaceInfo.namespace); @@ -23,6 +24,12 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: }; getVersion(); }, []); + React.useEffect(() => { + if (namespace && namespace != namespaceInfo.namespace) { + props.changeNamespace(namespace); + setNsInput(namespace); + } + }, []); return ( @@ -54,8 +61,9 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: placeholder='Namespace' onChange={(el) => setNsInput(el.target.value)} onItemClick={(val) => { - props.changeNamespace(val ? val : nsInput); - history.push(`/`); + const selectedNamespace = val ? val : nsInput; + props.changeNamespace(selectedNamespace); + history.push(`/${selectedNamespace}`); }} value={nsInput} /> @@ -65,4 +73,4 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: ); -}; \ No newline at end of file +}; diff --git a/ui/src/app/components/rollout/rollout.tsx b/ui/src/app/components/rollout/rollout.tsx index 9b2a962244..6c3f3a0393 100644 --- a/ui/src/app/components/rollout/rollout.tsx +++ b/ui/src/app/components/rollout/rollout.tsx @@ -5,7 +5,9 @@ import {Key, KeybindingContext} from 'react-keyhooks'; import {useHistory, useParams} from 'react-router-dom'; import { GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep, + GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1HeaderRoutingMatch, GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutExperimentTemplate, + GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute, RolloutReplicaSetInfo, RolloutRolloutInfo, RolloutServiceApi, @@ -128,11 +130,11 @@ export const RolloutWidget = (props: {rollout: RolloutRolloutInfo; interactive?: interactive={ interactive ? { - editState: interactive.editState, - setImage: (container, image, tag) => { - interactive.api.rolloutServiceSetRolloutImage({}, interactive.namespace, rollout.objectMeta?.name, container, image, tag); - }, - } + editState: interactive.editState, + setImage: (container, image, tag) => { + interactive.api.rolloutServiceSetRolloutImage({}, interactive.namespace, rollout.objectMeta?.name, container, image, tag); + }, + } : null } /> @@ -279,6 +281,8 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 const [openedTemplate, setOpenedTemplate] = React.useState(''); const [openCanary, setOpenCanary] = React.useState(false); const [openAnalysis, setOpenAnalysis] = React.useState(false); + const [openHeader, setOpenHeader] = React.useState(false); + const [openMirror, setOpenMirror] = React.useState(false); let icon: string; let content = ''; @@ -308,12 +312,27 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 icon = 'fa-flask'; } + if (props.step.setMirrorRoute) { + content = `Set Mirror: ${props.step.setMirrorRoute.name}`; + if(!props.step.setMirrorRoute.match) { + content = `Remove Mirror: ${props.step.setMirrorRoute.name}`; + } + } + + if (props.step.setHeaderRoute) { + content = `Set Header: ${props.step.setHeaderRoute.name}`; + if (!props.step.setHeaderRoute.match) { + content = `Remove Header: ${props.step.setHeaderRoute.name}`; + } + } + return (
{icon && } {content} {unit} @@ -327,6 +346,17 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 )} + + {props.step.setHeaderRoute && props.step.setHeaderRoute.match &&( + setOpenHeader(!openHeader)}> + + + )} + {props.step.setMirrorRoute && props.step.setMirrorRoute.match && ( + setOpenMirror(!openMirror)}> + + + )}
{props.step.experiment?.templates && (
@@ -351,17 +381,15 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1
)} {props.step?.setCanaryScale && openCanary && } + {props.step?.setHeaderRoute && openHeader && } + {props.step?.setMirrorRoute && openMirror && }
{!props.last && }
); }; -const ExperimentWidget = ({ - template, - opened, - onToggle, -}: { +const ExperimentWidget = ({template, opened, onToggle}: { template: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutExperimentTemplate; opened: boolean; onToggle: (name: string) => void; @@ -395,3 +423,81 @@ const WidgetItem = ({values}: {values: Record}) => { ); }; + +const WidgetItemSetMirror = ({value}: {value: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute}) => { + if (!value) return null; + return ( + + +
Name
+
{value.name}
+
Percentage
+
{value.percentage}
+ {Object.values(value.match).map((val, index) => { + if (!val) return null; + let stringMatcherValue = "" + let stringMatcherType = "" + let fragments = [] + if (val.path != null) { + if(val.path.exact != null) {stringMatcherValue = val.path.exact; stringMatcherType="Exact"} + if(val.path.prefix != null) {stringMatcherValue = val.path.prefix; stringMatcherType="Prefix"} + if(val.path.regex != null) {stringMatcherValue = val.path.regex; stringMatcherType="Regex"} + fragments.push( + +
{index} - Path ({stringMatcherType})
+
{stringMatcherValue}
+
+ ); + } + if (val.method != null) { + if(val.method.exact != null) {stringMatcherValue = val.method.exact; stringMatcherType="Exact"} + if(val.method.prefix != null) {stringMatcherValue = val.method.prefix; stringMatcherType="Prefix"} + if(val.method.regex != null) {stringMatcherValue = val.method.regex; stringMatcherType="Regex"} + fragments.push( + +
{index} - Method ({stringMatcherType})
+
{stringMatcherValue}
+
+ ); + } + return fragments + })} +
+
+ ); +}; + +const WidgetItemSetHeader = ({values}: {values: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1HeaderRoutingMatch[]}) => { + if (!values) return null; + return ( + + {values.map((record) => { + if (!record.headerName) return null; + if (!record.headerValue) return null; + + let headerValue = "" + let headerValueType = "" + if (record.headerValue.regex) { + headerValue = record.headerValue.regex + headerValueType = "Regex" + } + if (record.headerValue.prefix) { + headerValue = record.headerValue.prefix + headerValueType = "Prefix" + } + if (record.headerValue.exact) { + headerValue = record.headerValue.exact + headerValueType = "Exact" + } + return ( + +
Name
+
{record.headerName}
+
{headerValueType}
+
{headerValue}
+
+ ); + })} +
+ ); +}; diff --git a/ui/src/app/components/rollouts-list/rollouts-list.tsx b/ui/src/app/components/rollouts-list/rollouts-list.tsx index dfbcb1d515..bd660ff471 100644 --- a/ui/src/app/components/rollouts-list/rollouts-list.tsx +++ b/ui/src/app/components/rollouts-list/rollouts-list.tsx @@ -29,6 +29,12 @@ export const RolloutsList = () => { const [filteredRollouts, setFilteredRollouts] = React.useState(rollouts); const [pos, nav, reset] = useNav(filteredRollouts.length); const [searchString, setSearchString, searchInput] = useAutocomplete(''); + const searchParam = new URLSearchParams(window.location.search).get('q'); + React.useEffect(() => { + if (searchParam && searchParam != searchString) { + setSearchString(searchParam); + } + }, []); const {useKeybinding, keybindingState} = React.useContext(KeybindingContext); @@ -81,6 +87,9 @@ export const RolloutsList = () => { if ((filtered || []).length > 0) { setFilteredRollouts(filtered); } + if (searchString) { + history.replace(`/${namespaceCtx.namespace}?q=${searchString}`); + } }, [searchString, rollouts]); const namespaceCtx = React.useContext(NamespaceContext); @@ -97,7 +106,7 @@ export const RolloutsList = () => { className='rollouts-list__search' placeholder='Search...' style={{marginBottom: '1.5em'}} - onItemClick={(item) => history.push(`/rollout/${item}`)} + onItemClick={(item) => history.push(`/rollout/${namespaceCtx.namespace}/${item}`)} icon='fa-search' {...searchInput} /> @@ -176,7 +185,7 @@ export const RolloutWidget = (props: {rollout: RolloutInfo; deselect: () => void return ( - + { diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index 8de7ed3865..6416533dda 100644 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -526,10 +526,16 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep setCanaryScale?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetCanaryScale; /** * - * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRouting} + * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute} * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep */ - setHeaderRouting?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRouting; + setHeaderRoute?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute; + /** + * + * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep + */ + setMirrorRoute?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute; } /** * @@ -748,6 +754,19 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1IstioVirtua */ tlsRoutes?: Array; } +/** + * + * @export + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MangedRoutes + */ +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MangedRoutes { + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1MangedRoutes + */ + name?: string; +} /** * MeasurementRetention defines the settings for retaining the number of measurements during the analysis. * @export @@ -1483,6 +1502,37 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTraf * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTrafficRouting */ traefik?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1TraefikTrafficRouting; + /** + * A list of HTTP routes that Argo Rollouts manages, the order of this array also becomes the precedence in the upstream traffic router. + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RolloutTrafficRouting + */ + managedRoutes?: Array; +} +/** + * + * @export + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RouteMatch + */ +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RouteMatch { + /** + * + * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RouteMatch + */ + method?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch; + /** + * + * @type {GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RouteMatch + */ + path?: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch; + /** + * + * @type {{ [key: string]: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch; }} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1RouteMatch + */ + headers?: { [key: string]: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1StringMatch; }; } /** * @@ -1531,16 +1581,47 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetCanarySc /** * * @export - * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRouting + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute */ -export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRouting { +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute { + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute + */ + name?: string; /** * * @type {Array} - * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRouting + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetHeaderRoute */ match?: Array; } +/** + * + * @export + * @interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute + */ +export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute { + /** + * + * @type {string} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute + */ + name?: string; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute + */ + match?: Array; + /** + * + * @type {number} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1SetMirrorRoute + */ + percentage?: number; +} /** * * @export @@ -1891,7 +1972,7 @@ export interface K8sIoApiCoreV1AzureFileVolumeSource { */ export interface K8sIoApiCoreV1CSIVolumeSource { /** - * Driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. + * driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster. * @type {string} * @memberof K8sIoApiCoreV1CSIVolumeSource */ @@ -2681,7 +2762,7 @@ export interface K8sIoApiCoreV1FCVolumeSource { */ export interface K8sIoApiCoreV1FlexVolumeSource { /** - * Driver is the name of the driver to use for this volume. + * driver is the name of the driver to use for this volume. * @type {string} * @memberof K8sIoApiCoreV1FlexVolumeSource */ @@ -2931,19 +3012,19 @@ export interface K8sIoApiCoreV1HostPathVolumeSource { */ export interface K8sIoApiCoreV1ISCSIVolumeSource { /** - * iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). + * targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260). * @type {string} * @memberof K8sIoApiCoreV1ISCSIVolumeSource */ targetPortal?: string; /** - * Target iSCSI Qualified Name. + * iqn is the target iSCSI Qualified Name. * @type {string} * @memberof K8sIoApiCoreV1ISCSIVolumeSource */ iqn?: string; /** - * iSCSI Target Lun number. + * lun represents iSCSI Target Lun number. * @type {number} * @memberof K8sIoApiCoreV1ISCSIVolumeSource */ @@ -3004,13 +3085,13 @@ export interface K8sIoApiCoreV1ISCSIVolumeSource { */ export interface K8sIoApiCoreV1KeyToPath { /** - * The key to project. + * key is the key to project. * @type {string} * @memberof K8sIoApiCoreV1KeyToPath */ key?: string; /** - * The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. + * path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'. * @type {string} * @memberof K8sIoApiCoreV1KeyToPath */ @@ -3305,7 +3386,7 @@ export interface K8sIoApiCoreV1PhotonPersistentDiskVolumeSource { */ pdID?: string; /** - * Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. + * fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. * @type {string} * @memberof K8sIoApiCoreV1PhotonPersistentDiskVolumeSource */ @@ -3772,7 +3853,7 @@ export interface K8sIoApiCoreV1PortworxVolumeSource { */ volumeID?: string; /** - * FSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified. + * fSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified. * @type {string} * @memberof K8sIoApiCoreV1PortworxVolumeSource */ @@ -3915,7 +3996,7 @@ export interface K8sIoApiCoreV1QuobyteVolumeSource { */ registry?: string; /** - * Volume is a string that references an already created Quobyte volume by name. + * volume is a string that references an already created Quobyte volume by name. * @type {string} * @memberof K8sIoApiCoreV1QuobyteVolumeSource */ @@ -4082,13 +4163,13 @@ export interface K8sIoApiCoreV1SELinuxOptions { */ export interface K8sIoApiCoreV1ScaleIOVolumeSource { /** - * The host address of the ScaleIO API Gateway. + * gateway is the host address of the ScaleIO API Gateway. * @type {string} * @memberof K8sIoApiCoreV1ScaleIOVolumeSource */ gateway?: string; /** - * The name of the storage system as configured in ScaleIO. + * system is the name of the storage system as configured in ScaleIO. * @type {string} * @memberof K8sIoApiCoreV1ScaleIOVolumeSource */ @@ -4124,7 +4205,7 @@ export interface K8sIoApiCoreV1ScaleIOVolumeSource { */ storageMode?: string; /** - * The name of a volume already created in the ScaleIO system that is associated with this volume source. + * volumeName is the name of a volume already created in the ScaleIO system that is associated with this volume source. * @type {string} * @memberof K8sIoApiCoreV1ScaleIOVolumeSource */ @@ -4353,7 +4434,7 @@ export interface K8sIoApiCoreV1ServiceAccountTokenProjection { */ expirationSeconds?: string; /** - * Path is the path relative to the mount point of the file to project the token into. + * path is the path relative to the mount point of the file to project the token into. * @type {string} * @memberof K8sIoApiCoreV1ServiceAccountTokenProjection */ @@ -4366,7 +4447,7 @@ export interface K8sIoApiCoreV1ServiceAccountTokenProjection { */ export interface K8sIoApiCoreV1StorageOSVolumeSource { /** - * VolumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. + * volumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace. * @type {string} * @memberof K8sIoApiCoreV1StorageOSVolumeSource */ @@ -4478,13 +4559,13 @@ export interface K8sIoApiCoreV1Toleration { */ export interface K8sIoApiCoreV1TopologySpreadConstraint { /** - * MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: +-------+-------+-------+ | zone1 | zone2 | zone3 | +-------+-------+-------+ | P | P | | +-------+-------+-------+ - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 1/1/1; scheduling it onto zone1(zone2) would make the ActualSkew(2-0) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed. + * MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. +-------+-------+-------+ | zone1 | zone2 | zone3 | +-------+-------+-------+ | P P | P P | P | +-------+-------+-------+ - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed. * @type {number} * @memberof K8sIoApiCoreV1TopologySpreadConstraint */ maxSkew?: number; /** - * TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a \"bucket\", and try to put balanced number of pods into each bucket. It's a required field. + * TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a \"bucket\", and try to put balanced number of pods into each bucket. We define a domain as a particular instance of a topology. Also, we define an eligible domain as a domain whose nodes match the node selector. e.g. If TopologyKey is \"kubernetes.io/hostname\", each Node is a domain of that topology. And, if TopologyKey is \"topology.kubernetes.io/zone\", each zone is a domain of that topology. It's a required field. * @type {string} * @memberof K8sIoApiCoreV1TopologySpreadConstraint */ @@ -4501,6 +4582,12 @@ export interface K8sIoApiCoreV1TopologySpreadConstraint { * @memberof K8sIoApiCoreV1TopologySpreadConstraint */ labelSelector?: K8sIoApimachineryPkgApisMetaV1LabelSelector; + /** + * MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule. For example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: +-------+-------+-------+ | zone1 | zone2 | zone3 | +-------+-------+-------+ | P P | P P | P P | +-------+-------+-------+ The number of domains is less than 5(MinDomains), so \"global minimum\" is treated as 0. In this situation, new pod with the same labelSelector cannot be scheduled, because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, it will violate MaxSkew. This is an alpha field and requires enabling MinDomainsInPodTopologySpread feature gate. +optional + * @type {number} + * @memberof K8sIoApiCoreV1TopologySpreadConstraint + */ + minDomains?: number; } /** * @@ -5033,7 +5120,7 @@ export interface K8sIoApimachineryPkgApisMetaV1ObjectMeta { */ name?: string; /** - * GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. If this field is specified and the generated name exists, the server will NOT return a 409 - instead, it will either return 201 Created or 500 with Reason ServerTimeout indicating a unique name could not be found in the time allotted, and the client should retry (optionally after the time indicated in the Retry-After header). Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency +optional + * GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. If this field is specified and the generated name exists, the server will return a 409. Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency +optional * @type {string} * @memberof K8sIoApimachineryPkgApisMetaV1ObjectMeta */ @@ -5045,7 +5132,7 @@ export interface K8sIoApimachineryPkgApisMetaV1ObjectMeta { */ namespace?: string; /** - * SelfLink is a URL representing this object. Populated by the system. Read-only. DEPRECATED Kubernetes will stop propagating this field in 1.20 release and the field is planned to be removed in 1.21 release. +optional + * * @type {string} * @memberof K8sIoApimachineryPkgApisMetaV1ObjectMeta */ @@ -5111,7 +5198,7 @@ export interface K8sIoApimachineryPkgApisMetaV1ObjectMeta { */ finalizers?: Array; /** - * + * Deprecated: ClusterName is a legacy field that was always cleared by the system and never used; it will be removed completely in 1.25. The name in the go struct is changed to help clients detect accidental use. +optional * @type {string} * @memberof K8sIoApimachineryPkgApisMetaV1ObjectMeta */ diff --git a/utils/analysis/factory.go b/utils/analysis/factory.go index b500bfd30d..0fb57030f5 100644 --- a/utils/analysis/factory.go +++ b/utils/analysis/factory.go @@ -219,6 +219,12 @@ func ValidateMetric(metric v1alpha1.Metric) error { if metric.Provider.CloudWatch != nil { numProviders++ } + if metric.Provider.Graphite != nil { + numProviders++ + } + if metric.Provider.Influxdb != nil { + numProviders++ + } if numProviders == 0 { return fmt.Errorf("no provider specified") } diff --git a/utils/analysis/factory_test.go b/utils/analysis/factory_test.go index 21b55fb592..c485081999 100644 --- a/utils/analysis/factory_test.go +++ b/utils/analysis/factory_test.go @@ -398,6 +398,8 @@ func TestValidateMetrics(t *testing.T) { Datadog: &v1alpha1.DatadogMetric{}, NewRelic: &v1alpha1.NewRelicMetric{}, CloudWatch: &v1alpha1.CloudWatchMetric{}, + Graphite: &v1alpha1.GraphiteMetric{}, + Influxdb: &v1alpha1.InfluxdbMetric{}, }, }, }, diff --git a/utils/analysis/helpers.go b/utils/analysis/helpers.go index 64506ebae0..77583136af 100644 --- a/utils/analysis/helpers.go +++ b/utils/analysis/helpers.go @@ -87,7 +87,9 @@ func IsTerminating(run *v1alpha1.AnalysisRun) bool { for _, res := range run.Status.MetricResults { switch res.Phase { case v1alpha1.AnalysisPhaseFailed, v1alpha1.AnalysisPhaseError, v1alpha1.AnalysisPhaseInconclusive: - return true + // If this metric is running in the dryRun mode then we don't care about the failures and hence the terminal + // decision shouldn't be affected. + return !res.DryRun } } return false diff --git a/utils/analysis/helpers_test.go b/utils/analysis/helpers_test.go index c7bd003c18..f9baf89f14 100644 --- a/utils/analysis/helpers_test.go +++ b/utils/analysis/helpers_test.go @@ -62,20 +62,34 @@ func TestIsFastFailTerminating(t *testing.T) { Name: "success-rate", Phase: v1alpha1.AnalysisPhaseRunning, }, + { + Name: "dry-run-metric", + Phase: v1alpha1.AnalysisPhaseRunning, + DryRun: true, + }, }, }, } + // Verify that when the metric is not failing or in the error state then we don't terminate. successRate := run.Status.MetricResults[1] assert.False(t, IsTerminating(run)) + // Metric failing in the dryRun mode shouldn't impact the terminal decision. + dryRunMetricResult := run.Status.MetricResults[2] + dryRunMetricResult.Phase = v1alpha1.AnalysisPhaseError + run.Status.MetricResults[2] = dryRunMetricResult + assert.False(t, IsTerminating(run)) + // Verify that a wet run metric failure/error results in terminal decision. successRate.Phase = v1alpha1.AnalysisPhaseError run.Status.MetricResults[1] = successRate assert.True(t, IsTerminating(run)) successRate.Phase = v1alpha1.AnalysisPhaseFailed run.Status.MetricResults[1] = successRate assert.True(t, IsTerminating(run)) + // Verify that an inconclusive wet run metric results in terminal decision. successRate.Phase = v1alpha1.AnalysisPhaseInconclusive run.Status.MetricResults[1] = successRate assert.True(t, IsTerminating(run)) + // Verify that we don't terminate when there are no metric results or when the status is empty. run.Status.MetricResults = nil assert.False(t, IsTerminating(run)) run.Status = v1alpha1.AnalysisRunStatus{} diff --git a/utils/aws/mocks/ELBv2APIClient.go b/utils/aws/mocks/ELBv2APIClient.go index 7f621b0563..cfc8872f3b 100644 --- a/utils/aws/mocks/ELBv2APIClient.go +++ b/utils/aws/mocks/ELBv2APIClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v0.0.0-dev. DO NOT EDIT. +// Code generated by mockery v2.14.0. DO NOT EDIT. package mocks @@ -193,3 +193,18 @@ func (_m *ELBv2APIClient) DescribeTargetHealth(ctx context.Context, params *elas return r0, r1 } + +type mockConstructorTestingTNewELBv2APIClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewELBv2APIClient creates a new instance of ELBv2APIClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewELBv2APIClient(t mockConstructorTestingTNewELBv2APIClient) *ELBv2APIClient { + mock := &ELBv2APIClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index 03341742f5..c2aaece787 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -68,6 +68,17 @@ const ( RolloutCompletedReason = "RolloutCompleted" // RolloutCompletedMessage is added when the rollout is completed RolloutCompletedMessage = "Rollout completed update to revision %d (%s): %s" + // RolloutNotCompletedReason is added in a rollout when it is completed. + RolloutNotCompletedReason = "RolloutNotCompleted" + // RolloutNotCompletedMessage is added when the rollout is completed + RolloutNotCompletedMessage = "Rollout not completed, started update to revision %d (%s)" + + // RolloutHealthyReason is added in a rollout when it is healthy. + RolloutHealthyReason = "RolloutHealthy" + // RolloutHealthyMessage is added when the rollout is completed and is healthy or not. + RolloutHealthyMessage = "Rollout is healthy" + // RolloutNotHealthyMessage is added when the rollout is completed and is healthy or not. + RolloutNotHealthyMessage = "Rollout is not healthy" // RolloutAbortedReason indicates that the rollout was aborted RolloutAbortedReason = "RolloutAborted" @@ -125,10 +136,13 @@ const ( // TimedOutReason is added in a rollout when its newest replica set fails to show any progress // within the given deadline (progressDeadlineSeconds). TimedOutReason = "ProgressDeadlineExceeded" - // RolloutTimeOutMessage is is added in a rollout when the rollout fails to show any progress + // RolloutTimeOutMessage is added in a rollout when the rollout fails to show any progress // within the given deadline (progressDeadlineSeconds). RolloutTimeOutMessage = "Rollout %q has timed out progressing." + RolloutDeletedReason = "RolloutDeleted" + RolloutDeletedMessage = "Rollout %s/%s is deleted." + ScalingReplicaSetReason = "ScalingReplicaSet" ScalingReplicaSetMessage = "Scaled %s ReplicaSet %s (revision %d) from %d to %d" @@ -254,9 +268,9 @@ func RolloutProgressing(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutSt strategySpecificProgress } -// RolloutComplete considers a rollout to be complete once all of its desired replicas +// RolloutHealthy considers a rollout to be healthy once all of its desired replicas // are updated, available, and receiving traffic from the active service, and no old pods are running. -func RolloutComplete(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) bool { +func RolloutHealthy(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) bool { completedStrategy := true replicas := defaults.GetReplicasOrDefault(rollout.Spec.Replicas) @@ -285,6 +299,11 @@ func RolloutComplete(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatu completedStrategy } +// RolloutCompleted considers a rollout to be complete once StableRS == CurrentPodHash +func RolloutCompleted(rollout *v1alpha1.Rollout, newStatus *v1alpha1.RolloutStatus) bool { + return newStatus.StableRS != "" && newStatus.StableRS == newStatus.CurrentPodHash +} + // ComputeStepHash returns a hash value calculated from the Rollout's steps. The hash will // be safe encoded to avoid bad words. func ComputeStepHash(rollout *v1alpha1.Rollout) string { diff --git a/utils/conditions/rollouts_test.go b/utils/conditions/rollouts_test.go index f6e3e02550..842f01f594 100644 --- a/utils/conditions/rollouts_test.go +++ b/utils/conditions/rollouts_test.go @@ -350,7 +350,7 @@ func TestRolloutProgressing(t *testing.T) { } -func TestRolloutComplete(t *testing.T) { +func TestRolloutHealthy(t *testing.T) { rollout := func(desired, current, updated, available int32, correctObservedGeneration bool) *v1alpha1.Rollout { r := &v1alpha1.Rollout{ Spec: v1alpha1.RolloutSpec{ @@ -475,12 +475,36 @@ func TestRolloutComplete(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.expected, RolloutComplete(test.r, &test.r.Status)) + assert.Equal(t, test.expected, RolloutHealthy(test.r, &test.r.Status)) }) } } +func TestRolloutComplete(t *testing.T) { + rollout := func(desired, current, updated, available int32) *v1alpha1.Rollout { + r := &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Replicas: &desired, + }, + Status: v1alpha1.RolloutStatus{ + Replicas: current, + UpdatedReplicas: updated, + AvailableReplicas: available, + }, + } + podHash := hash.ComputePodTemplateHash(&r.Spec.Template, r.Status.CollisionCount) + r.Status.CurrentPodHash = podHash + r.Status.StableRS = podHash + return r + } + r := rollout(5, 5, 5, 5) + assert.Equal(t, true, RolloutCompleted(r, &r.Status)) + + r.Status.StableRS = "not-current-pod-hash" + assert.Equal(t, false, RolloutCompleted(r, &r.Status)) +} + func TestRolloutTimedOut(t *testing.T) { before := metav1.Time{ diff --git a/utils/defaults/defaults.go b/utils/defaults/defaults.go index 79e208671f..c38a044c25 100644 --- a/utils/defaults/defaults.go +++ b/utils/defaults/defaults.go @@ -42,6 +42,9 @@ const ( DefaultBurst int = 80 // DefaultAwsLoadBalancerPageSize is the default page size used when calling aws to get load balancers by DNS name DefaultAwsLoadBalancerPageSize = int32(300) + // DefaultMetricCleanupDelay is the default time to delay metrics removal upon object removal, gives time for metrics + // to be collected + DefaultMetricCleanupDelay = int32(65) ) const ( @@ -62,6 +65,7 @@ var ( smiAPIVersion = DefaultSMITrafficSplitVersion targetGroupBindingAPIVersion = DefaultTargetGroupBindingAPIVersion appmeshCRDVersion = DefaultAppMeshCRDVersion + defaultMetricCleanupDelay = DefaultMetricCleanupDelay ) const ( @@ -296,3 +300,13 @@ func GetTargetGroupBindingAPIVersion() string { func GetRolloutVerifyRetryInterval() time.Duration { return rolloutVerifyRetryInterval } + +// GetMetricCleanupDelaySeconds returns the duration to delay the cleanup of metrics +func GetMetricCleanupDelaySeconds() time.Duration { + return time.Duration(defaultMetricCleanupDelay) * time.Second +} + +// SetMetricCleanupDelaySeconds sets the metric cleanup delay in seconds +func SetMetricCleanupDelaySeconds(seconds int32) { + defaultMetricCleanupDelay = seconds +} diff --git a/utils/defaults/defaults_test.go b/utils/defaults/defaults_test.go index f5a429f202..d099cc0343 100644 --- a/utils/defaults/defaults_test.go +++ b/utils/defaults/defaults_test.go @@ -402,4 +402,8 @@ func TestSetDefaults(t *testing.T) { SetAppMeshCRDVersion("v1beta3") assert.Equal(t, "v1beta3", GetAppMeshCRDVersion()) SetAppMeshCRDVersion(DefaultAmbassadorVersion) + + assert.Equal(t, DefaultMetricCleanupDelay, int32(GetMetricCleanupDelaySeconds().Seconds())) + SetMetricCleanupDelaySeconds(24) + assert.Equal(t, time.Duration(24)*time.Second, GetMetricCleanupDelaySeconds()) } diff --git a/utils/ingress/wrapper.go b/utils/ingress/wrapper.go index 1d28c8021d..a460baac70 100644 --- a/utils/ingress/wrapper.go +++ b/utils/ingress/wrapper.go @@ -95,6 +95,27 @@ func (i *Ingress) GetAnnotations() map[string]string { } } +// GetClass returns the ingress class. +// For backwards compatibility `kubernetes.io/ingress.class` annotation will be used if set, +// otherwise `spec.ingressClassName` is used. +func (i *Ingress) GetClass() string { + annotations := i.GetAnnotations() + class := annotations["kubernetes.io/ingress.class"] + if class == "" { + switch i.mode { + case IngressModeNetworking: + if c := i.ingress.Spec.IngressClassName; c != nil { + class = *c + } + case IngressModeExtensions: + if c := i.legacyIngress.Spec.IngressClassName; c != nil { + class = *c + } + } + } + return class +} + func (i *Ingress) GetLabels() map[string]string { switch i.mode { case IngressModeNetworking: diff --git a/utils/ingress/wrapper_test.go b/utils/ingress/wrapper_test.go index eae08b4828..8611dcd395 100644 --- a/utils/ingress/wrapper_test.go +++ b/utils/ingress/wrapper_test.go @@ -124,6 +124,93 @@ func TestGetNetworkingIngress(t *testing.T) { }) } +func TestGetClass(t *testing.T) { + t.Run("will get the class from network Ingress annotation", func(t *testing.T) { + // given + t.Parallel() + i := getNetworkingIngress() + annotations := map[string]string{"kubernetes.io/ingress.class": "ingress-name-annotation"} + i.SetAnnotations(annotations) + emptyClass := "" + i.Spec.IngressClassName = &emptyClass + w := ingress.NewIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name-annotation", class) + }) + t.Run("will get the class from network Ingress annotation with priority", func(t *testing.T) { + // given + t.Parallel() + i := getNetworkingIngress() + annotations := map[string]string{"kubernetes.io/ingress.class": "ingress-name-annotation"} + i.SetAnnotations(annotations) + w := ingress.NewIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name-annotation", class) + }) + t.Run("will get the class from network Ingress spec", func(t *testing.T) { + // given + t.Parallel() + i := getNetworkingIngress() + w := ingress.NewIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name", class) + }) + t.Run("will get the class from extensions Ingress annotation", func(t *testing.T) { + // given + t.Parallel() + i := getExtensionsIngress() + annotations := map[string]string{"kubernetes.io/ingress.class": "ingress-name-annotation"} + i.SetAnnotations(annotations) + emptyClass := "" + i.Spec.IngressClassName = &emptyClass + w := ingress.NewLegacyIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name-annotation", class) + }) + t.Run("will get the class from extensions Ingress annotation with priority", func(t *testing.T) { + // given + t.Parallel() + i := getExtensionsIngress() + annotations := map[string]string{"kubernetes.io/ingress.class": "ingress-name-annotation"} + i.SetAnnotations(annotations) + w := ingress.NewLegacyIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name-annotation", class) + }) + t.Run("will get the class from extensions Ingress spec", func(t *testing.T) { + // given + t.Parallel() + i := getExtensionsIngress() + w := ingress.NewLegacyIngress(i) + + // when + class := w.GetClass() + + // then + assert.Equal(t, "ingress-name", class) + }) +} + func TestGetLabels(t *testing.T) { t.Run("will get the labels from network Ingress successfully", func(t *testing.T) { // given diff --git a/utils/record/record.go b/utils/record/record.go index 3ad05671b2..f255b02c24 100644 --- a/utils/record/record.go +++ b/utils/record/record.go @@ -2,6 +2,8 @@ package record import ( "context" + "crypto/sha1" + "encoding/base64" "encoding/json" "regexp" "strings" @@ -266,29 +268,32 @@ func (e *EventRecorderAdapter) sendNotifications(object runtime.Object, opts Eve return nil } - // Creates config for notifications for built-in triggers - triggerActions, ok := cfg.Triggers[trigger] - if !ok { - logCtx.Debugf("No configured template for trigger: %s", trigger) - return nil - } - objMap, err := toObjectMap(object) if err != nil { return err } - res, err := notificationsAPI.RunTrigger(trigger, objMap) - if err != nil { - log.Errorf("Failed to execute condition of trigger %s: %v", trigger, err) - return err - } - log.Infof("Trigger %s result: %v", trigger, res) + emptyCondition := hash("") + + for _, destination := range destinations { + res, err := notificationsAPI.RunTrigger(trigger, objMap) + if err != nil { + log.Errorf("Failed to execute condition of trigger %s: %v", trigger, err) + return err + } + log.Infof("Trigger %s result: %v", trigger, res) - for _, dest := range destinations { for _, c := range res { - if c.Triggered == true { - err = notificationsAPI.Send(objMap, triggerActions[0].Send, dest) + log.Infof("Res When Condition hash: %s, Templates: %s", c.Key, c.Templates) + s := strings.Split(c.Key, ".")[1] + if s != emptyCondition && c.Triggered == true { + err = notificationsAPI.Send(objMap, c.Templates, destination) + if err != nil { + log.Errorf("notification error: %s", err.Error()) + return err + } + } else if s == emptyCondition { + err = notificationsAPI.Send(objMap, c.Templates, destination) if err != nil { log.Errorf("notification error: %s", err.Error()) return err @@ -296,9 +301,19 @@ func (e *EventRecorderAdapter) sendNotifications(object runtime.Object, opts Eve } } } + return nil } +// This function is copied over from notification engine to make sure we honour emptyCondition +// emptyConditions today are not handled well in notification engine. +// TODO: update notification engine to handle emptyConditions and remove this function and its usage +func hash(input string) string { + h := sha1.New() + _, _ = h.Write([]byte(input)) + return base64.RawURLEncoding.EncodeToString(h.Sum(nil)) +} + // toObjectMap converts an object to a map for the purposes of sending to the notification engine func toObjectMap(object interface{}) (map[string]interface{}, error) { objBytes, err := json.Marshal(object) diff --git a/utils/record/record_test.go b/utils/record/record_test.go index 931ce42c9b..c99f55985b 100644 --- a/utils/record/record_test.go +++ b/utils/record/record_test.go @@ -96,7 +96,7 @@ func TestSendNotifications(t *testing.T) { mockCtrl := gomock.NewController(t) mockAPI := mocks.NewMockAPI(mockCtrl) cr := []triggers.ConditionResult{{ - Key: "", + Key: "1." + hash(""), Triggered: true, Templates: []string{"my-template"}, }} @@ -112,6 +112,33 @@ func TestSendNotifications(t *testing.T) { assert.NoError(t, err) } +func TestSendNotificationsWhenCondition(t *testing.T) { + r := v1alpha1.Rollout{ + ObjectMeta: metav1.ObjectMeta{ + Name: "guestbook", + Namespace: "default", + Annotations: map[string]string{"notifications.argoproj.io/subscribe.on-foo-reason.console": "console"}, + }, + } + mockCtrl := gomock.NewController(t) + mockAPI := mocks.NewMockAPI(mockCtrl) + cr := []triggers.ConditionResult{{ + Key: "1." + hash(""), + Triggered: true, + Templates: []string{"my-template"}, + }} + mockAPI.EXPECT().RunTrigger(gomock.Any(), gomock.Any()).Return(cr, nil).AnyTimes() + mockAPI.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + mockAPI.EXPECT().GetConfig().Return(api.Config{ + Triggers: map[string][]triggers.Condition{"on-foo-reason": {triggers.Condition{When: "rollout.spec.template.spec.containers[0].image == test:blue", Send: []string{"my-template"}}}}}).AnyTimes() + apiFactory := &mocks.FakeFactory{Api: mockAPI} + rec := NewFakeEventRecorder() + rec.EventRecorderAdapter.apiFactory = apiFactory + //ch := make(chan prometheus.HistogramVec, 1) + err := rec.sendNotifications(&r, EventOptions{EventReason: "FooReason"}) + assert.NoError(t, err) +} + func TestNotificationFailedCounter(t *testing.T) { r := v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ @@ -201,7 +228,7 @@ func TestSendNotificationsFails(t *testing.T) { mockCtrl := gomock.NewController(t) mockAPI := mocks.NewMockAPI(mockCtrl) cr := []triggers.ConditionResult{{ - Key: "", + Key: "1." + hash(""), Triggered: true, Templates: []string{"my-template"}, }} @@ -241,7 +268,7 @@ func TestSendNotificationsFailsWithRunTriggerError(t *testing.T) { mockCtrl := gomock.NewController(t) mockAPI := mocks.NewMockAPI(mockCtrl) cr := []triggers.ConditionResult{{ - Key: "", + Key: "1." + hash(""), Triggered: true, Templates: []string{"my-template"}, }} @@ -279,6 +306,12 @@ func TestSendNotificationsNoTrigger(t *testing.T) { mockCtrl := gomock.NewController(t) mockAPI := mocks.NewMockAPI(mockCtrl) + cr := []triggers.ConditionResult{{ + Key: "1." + hash(""), + Triggered: false, + Templates: []string{"my-template"}, + }} + mockAPI.EXPECT().RunTrigger(gomock.Any(), gomock.Any()).Return(cr, errors.New("trigger 'on-missing-reason' is not configured")).AnyTimes() mockAPI.EXPECT().GetConfig().Return(api.Config{ Triggers: map[string][]triggers.Condition{"on-foo-reason": {triggers.Condition{Send: []string{"my-template"}}}}}).AnyTimes() mockAPI.EXPECT().Send(gomock.Any(), gomock.Any(), gomock.Any()).Return(fmt.Errorf("failed to send")).Times(0) @@ -287,7 +320,7 @@ func TestSendNotificationsNoTrigger(t *testing.T) { rec.EventRecorderAdapter.apiFactory = apiFactory err := rec.sendNotifications(&r, EventOptions{EventReason: "MissingReason"}) - assert.NoError(t, err) + assert.Error(t, err) } func TestNewAPIFactorySettings(t *testing.T) { diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go index 8a015f7b7d..ae77bc04f0 100644 --- a/utils/replicaset/canary.go +++ b/utils/replicaset/canary.go @@ -474,19 +474,6 @@ func GetCurrentSetWeight(rollout *v1alpha1.Rollout) int32 { return 0 } -func GetCurrentSetHeaderRouting(rollout *v1alpha1.Rollout, index int32) *v1alpha1.SetHeaderRouting { - if int32(len(rollout.Spec.Strategy.Canary.Steps)) == index { - index-- - } - for i := index; i >= 0; i-- { - step := rollout.Spec.Strategy.Canary.Steps[i] - if step.SetHeaderRouting != nil { - return step.SetHeaderRouting - } - } - return nil -} - // UseSetCanaryScale will return a SetCanaryScale if specified and should be used, returns nil otherwise. // TrafficRouting is required to be set for SetCanaryScale to be applicable. // If MatchTrafficWeight is set after a previous SetCanaryScale step, it will likewise be ignored. diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go index e111043362..b341bb06e6 100644 --- a/utils/replicaset/canary_test.go +++ b/utils/replicaset/canary_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - "github.com/aws/smithy-go/ptr" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -1015,41 +1014,6 @@ func TestGetCurrentSetWeight(t *testing.T) { } -func TestGetCurrentSetHeaderRouting(t *testing.T) { - rollout := newRollout(10, 10, intstr.FromInt(0), intstr.FromInt(1), "", "", nil, nil) - setHeaderRoutingStep := v1alpha1.SetHeaderRouting{ - Match: []v1alpha1.HeaderRoutingMatch{ - { - HeaderName: "agent", - }, - { - HeaderName: "agent2", - HeaderValue: v1alpha1.StringMatch{Exact: "value"}, - }, - { - HeaderName: "agent3", - HeaderValue: v1alpha1.StringMatch{Regex: "regexValue(.*)"}, - }, - }, - } - rollout.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{ - {SetWeight: ptr.Int32(20)}, - {Pause: &v1alpha1.RolloutPause{}}, - {SetHeaderRouting: &setHeaderRoutingStep}, - {SetWeight: ptr.Int32(40)}, - {Pause: &v1alpha1.RolloutPause{}}, - {SetHeaderRouting: &v1alpha1.SetHeaderRouting{}}, - } - - assert.Nil(t, GetCurrentSetHeaderRouting(rollout, 0)) - assert.Nil(t, GetCurrentSetHeaderRouting(rollout, 1)) - assert.Equal(t, &setHeaderRoutingStep, GetCurrentSetHeaderRouting(rollout, 2)) - assert.Equal(t, &setHeaderRoutingStep, GetCurrentSetHeaderRouting(rollout, 3)) - assert.Equal(t, &setHeaderRoutingStep, GetCurrentSetHeaderRouting(rollout, 4)) - assert.Nil(t, GetCurrentSetHeaderRouting(rollout, 5).Match) - assert.Nil(t, GetCurrentSetHeaderRouting(rollout, 6).Match) -} - func TestAtDesiredReplicaCountsForCanary(t *testing.T) { t.Run("we are at desired replica counts and availability", func(t *testing.T) { diff --git a/utils/rollout/rolloututil.go b/utils/rollout/rolloututil.go index c9243c3215..0b7df7ff38 100644 --- a/utils/rollout/rolloututil.go +++ b/utils/rollout/rolloututil.go @@ -4,6 +4,8 @@ import ( "fmt" "strconv" + replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/annotations" "github.com/argoproj/argo-rollouts/utils/conditions" @@ -179,3 +181,16 @@ func CanaryStepString(c v1alpha1.CanaryStep) string { } return "invalid" } + +// ShouldVerifyWeight We use this to test if we should verify weights because weight verification could involve +// API calls to the cloud provider which could incur rate limiting +func ShouldVerifyWeight(ro *v1alpha1.Rollout) bool { + currentStep, _ := replicasetutil.GetCurrentCanaryStep(ro) + // If we are in the middle of an update at a setWeight step, also perform weight verification. + // Note that we don't do this every reconciliation because weight verification typically involves + // API calls to the cloud provider which could incur rate limitingq + shouldVerifyWeight := ro.Status.StableRS != "" && + !IsFullyPromoted(ro) && + currentStep != nil && currentStep.SetWeight != nil + return shouldVerifyWeight +} diff --git a/utils/rollout/rolloututil_test.go b/utils/rollout/rolloututil_test.go index efb76af9a3..572bd1cb51 100644 --- a/utils/rollout/rolloututil_test.go +++ b/utils/rollout/rolloututil_test.go @@ -414,3 +414,21 @@ func TestIsUnpausing(t *testing.T) { assert.Equal(t, v1alpha1.RolloutPhaseProgressing, status) assert.Equal(t, "waiting for rollout to unpause", message) } + +func TestShouldVerifyWeight(t *testing.T) { + ro := newCanaryRollout() + ro.Status.StableRS = "34feab23f" + ro.Status.CurrentStepIndex = pointer.Int32Ptr(0) + ro.Spec.Strategy.Canary.Steps = []v1alpha1.CanaryStep{{ + SetWeight: pointer.Int32Ptr(20), + }} + assert.Equal(t, true, ShouldVerifyWeight(ro)) + + ro.Status.StableRS = "" + assert.Equal(t, false, ShouldVerifyWeight(ro)) + + ro.Status.StableRS = "34feab23f" + ro.Status.CurrentStepIndex = nil + ro.Spec.Strategy.Canary.Steps = nil + assert.Equal(t, false, ShouldVerifyWeight(ro)) +} diff --git a/utils/unstructured/unstructured.go b/utils/unstructured/unstructured.go index e92fb00e81..12e1034755 100644 --- a/utils/unstructured/unstructured.go +++ b/utils/unstructured/unstructured.go @@ -67,6 +67,24 @@ func ObjectToAnalysisRun(obj interface{}) *v1alpha1.AnalysisRun { return ar } +func ObjectToExperiment(obj interface{}) *v1alpha1.Experiment { + un, ok := obj.(*unstructured.Unstructured) + if ok { + var ex v1alpha1.Experiment + err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &ex) + if err != nil { + log.Warnf("Failed to convert Experiment from Unstructured object: %v", err) + return nil + } + return &ex + } + ex, ok := obj.(*v1alpha1.Experiment) + if !ok { + log.Warn("Object is neither a rollout or unstructured") + } + return ex +} + var diffSeparator = regexp.MustCompile(`\n---`) // SplitYAML splits a YAML file into unstructured objects. Returns list of all unstructured objects diff --git a/utils/unstructured/unstructured_test.go b/utils/unstructured/unstructured_test.go index 5472646130..1dd9562c2c 100644 --- a/utils/unstructured/unstructured_test.go +++ b/utils/unstructured/unstructured_test.go @@ -155,11 +155,63 @@ spec: obj, err := StrToUnstructured(arYAML) assert.NotNil(t, obj) assert.NoError(t, err) - ar := ObjectToRollout(obj) + ar := ObjectToAnalysisRun(obj) assert.NotNil(t, ar) - ar2 := ObjectToRollout(ar) + ar2 := ObjectToAnalysisRun(ar) assert.Equal(t, ar, ar2) var invalid struct{} - ar3 := ObjectToRollout(&invalid) + ar3 := ObjectToAnalysisRun(&invalid) assert.Nil(t, ar3) } + +func TestObjectToExpirment(t *testing.T) { + exYAML := ` +apiVersion: argoproj.io/v1alpha1 +kind: Experiment +metadata: + name: experiment-with-analysis +spec: + templates: + - name: purple + selector: + matchLabels: + app: rollouts-demo + template: + metadata: + labels: + app: rollouts-demo + spec: + containers: + - name: rollouts-demo + image: argoproj/rollouts-demo:purple + imagePullPolicy: Always + - name: orange + selector: + matchLabels: + app: rollouts-demo + template: + metadata: + labels: + app: rollouts-demo + spec: + containers: + - name: rollouts-demo + image: argoproj/rollouts-demo:orange + imagePullPolicy: Always + analyses: + - name: random-fail + templateName: random-fail + - name: pass + templateName: pass +` + obj, err := StrToUnstructured(exYAML) + assert.NotNil(t, obj) + assert.NoError(t, err) + ex := ObjectToExperiment(obj) + assert.NotNil(t, ex) + ex2 := ObjectToExperiment(ex) + assert.Equal(t, ex, ex2) + var invalid struct{} + ex3 := ObjectToExperiment(&invalid) + assert.Nil(t, ex3) +}