From 43eaf3c9f40f0bb824fa031bf120b45decbb58aa Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Thu, 29 Jul 2021 14:44:41 -0700 Subject: [PATCH 01/34] fix: nil pointer dereference when reconciling paused blue-green rollout (#1378) Signed-off-by: Jesse Suen --- rollout/bluegreen.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index ef8d053628..fd8ab46848 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -162,22 +162,24 @@ func (c *rolloutContext) reconcileBlueGreenPause(activeSvc, previewSvc *corev1.S return } pauseCond := getPauseCondition(c.rollout, v1alpha1.PauseReasonBlueGreenPause) - if pauseCond == nil && !c.rollout.Status.ControllerPause { - if pauseCond == nil { - c.log.Info("pausing") - } - c.pauseContext.AddPauseCondition(v1alpha1.PauseReasonBlueGreenPause) - return - } - - if !c.pauseContext.CompletedBlueGreenPause() { - c.log.Info("pause incomplete") - if c.rollout.Spec.Strategy.BlueGreen.AutoPromotionSeconds > 0 { - c.checkEnqueueRolloutDuringWait(pauseCond.StartTime, c.rollout.Spec.Strategy.BlueGreen.AutoPromotionSeconds) + if pauseCond != nil { + // We are currently paused. Check if we completed our pause duration + if !c.pauseContext.CompletedBlueGreenPause() { + c.log.Info("pause incomplete") + if c.rollout.Spec.Strategy.BlueGreen.AutoPromotionSeconds > 0 { + c.checkEnqueueRolloutDuringWait(pauseCond.StartTime, c.rollout.Spec.Strategy.BlueGreen.AutoPromotionSeconds) + } + } else { + c.log.Infof("pause completed") + c.pauseContext.RemovePauseCondition(v1alpha1.PauseReasonBlueGreenPause) } } else { - c.log.Infof("pause completed") - c.pauseContext.RemovePauseCondition(v1alpha1.PauseReasonBlueGreenPause) + // no pause condition exists. If Status.ControllerPause is true, the user manually resumed + // the rollout. e.g. `kubectl argo rollouts promote ROLLOUT` + if !c.rollout.Status.ControllerPause { + c.log.Info("pausing") + c.pauseContext.AddPauseCondition(v1alpha1.PauseReasonBlueGreenPause) + } } } From ff3569dc3977b952058acd2ea3fa674716bff5f6 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Fri, 30 Jul 2021 16:25:37 -0700 Subject: [PATCH 02/34] fix: Promote full did not work against BlueGreen with previewReplicaCount (#1384) Signed-off-by: Jesse Suen --- test/e2e/bluegreen_test.go | 100 ++++++++++++++++++++++++++++ test/e2e/functional_test.go | 51 -------------- test/fixtures/when.go | 1 + utils/replicaset/replicaset.go | 18 +++-- utils/replicaset/replicaset_test.go | 8 +++ 5 files changed, 123 insertions(+), 55 deletions(-) diff --git a/test/e2e/bluegreen_test.go b/test/e2e/bluegreen_test.go index 9c91c6c0ef..cc3061987a 100644 --- a/test/e2e/bluegreen_test.go +++ b/test/e2e/bluegreen_test.go @@ -278,3 +278,103 @@ spec: Then(). ExpectActiveRevision("2") } + +// TestBlueGreenPreviewReplicaCount verifies the previewReplicaCount feature +func (s *BlueGreenSuite) TestBlueGreenPreviewReplicaCount() { + s.Given(). + RolloutObjects(newService("bluegreen-preview-replicas-active")). + RolloutObjects(newService("bluegreen-preview-replicas-preview")). + RolloutObjects(` +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: bluegreen-preview-replicas +spec: + replicas: 2 + strategy: + blueGreen: + activeService: bluegreen-preview-replicas-active + previewService: bluegreen-preview-replicas-preview + previewReplicaCount: 1 + scaleDownDelaySeconds: 5 + autoPromotionEnabled: false + selector: + matchLabels: + app: bluegreen-preview-replicas + template: + metadata: + labels: + app: bluegreen-preview-replicas + spec: + containers: + - name: bluegreen-preview-replicas + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m +`). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Then(). + ExpectRevisionPodCount("2", 1). + ExpectRevisionPodCount("1", 2). + ExpectReplicaCounts(2, 3, 1, 2, 2). // desired, current, updated, ready, available + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectReplicaCounts(2, 4, 2, 2, 2) +} + +// TestBlueGreenPreviewReplicaCountPromoteFull verifies promote full works with previewReplicaCount +func (s *FunctionalSuite) TestBlueGreenPreviewReplicaCountPromoteFull() { + s.Given(). + RolloutObjects(newService("bluegreen-preview-replicas-active")). + RolloutObjects(` +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: bluegreen-preview-replicas-promote-full +spec: + replicas: 2 + progressDeadlineSeconds: 1 # use a very short value to cause Degraded condition frequently + strategy: + blueGreen: + activeService: bluegreen-preview-replicas-active + previewReplicaCount: 1 + autoPromotionEnabled: false + selector: + matchLabels: + app: bluegreen-preview-replicas-promote-full + template: + metadata: + labels: + app: bluegreen-preview-replicas-promote-full + spec: + containers: + - name: bluegreen-preview-replicas-promote-full + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m +`). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Sleep(2*time.Second). // sleep for longer than progressDeadlineSeconds + Then(). + ExpectRolloutStatus("Paused"). // the fact that we are paused for longer than progressDeadlineSeconds, should not cause Degraded + ExpectReplicaCounts(2, 3, 1, 2, 2). // desired, current, updated, ready, available + When(). + PromoteRolloutFull(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectReplicaCounts(2, 4, 2, 2, 2) +} diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index f038fa0fce..fc381773f3 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -706,57 +706,6 @@ func (s *FunctionalSuite) TestBlueGreenUpdate() { }) } -// TestBlueGreenPreviewReplicaCount verifies the previewReplicaCount feature -func (s *FunctionalSuite) TestBlueGreenPreviewReplicaCount() { - s.Given(). - RolloutObjects(newService("bluegreen-preview-replicas-active")). - RolloutObjects(newService("bluegreen-preview-replicas-preview")). - RolloutObjects(` -apiVersion: argoproj.io/v1alpha1 -kind: Rollout -metadata: - name: bluegreen-preview-replicas -spec: - replicas: 2 - strategy: - blueGreen: - activeService: bluegreen-preview-replicas-active - previewService: bluegreen-preview-replicas-preview - previewReplicaCount: 1 - scaleDownDelaySeconds: 5 - autoPromotionEnabled: false - selector: - matchLabels: - app: bluegreen-preview-replicas - template: - metadata: - labels: - app: bluegreen-preview-replicas - spec: - containers: - - name: bluegreen-preview-replicas - image: nginx:1.19-alpine - resources: - requests: - memory: 16Mi - cpu: 1m -`). - When(). - ApplyManifests(). - WaitForRolloutStatus("Healthy"). - UpdateSpec(). - WaitForRolloutStatus("Paused"). - Then(). - ExpectRevisionPodCount("2", 1). - ExpectRevisionPodCount("1", 2). - ExpectReplicaCounts(2, 3, 1, 2, 2). // desired, current, updated, ready, available - When(). - PromoteRollout(). - WaitForRolloutStatus("Healthy"). - Then(). - ExpectReplicaCounts(2, 4, 2, 2, 2) -} - // TestBlueGreenToCanary tests behavior when migrating from bluegreen to canary func (s *FunctionalSuite) TestBlueGreenToCanary() { s.Given(). diff --git a/test/fixtures/when.go b/test/fixtures/when.go index eb6791e831..81d44628e9 100644 --- a/test/fixtures/when.go +++ b/test/fixtures/when.go @@ -189,6 +189,7 @@ func (w *When) ScaleRollout(scale int) *When { } func (w *When) Sleep(d time.Duration) *When { + w.log.Infof("Sleeping %s", d) time.Sleep(d) return w } diff --git a/utils/replicaset/replicaset.go b/utils/replicaset/replicaset.go index 5d1c8fabcf..598b301ad1 100644 --- a/utils/replicaset/replicaset.go +++ b/utils/replicaset/replicaset.go @@ -234,22 +234,32 @@ func FindOldReplicaSets(rollout *v1alpha1.Rollout, rsList []*appsv1.ReplicaSet) // 2) Max number of pods allowed is reached: deployment's replicas + maxSurge == all RSs' replicas func NewRSNewReplicas(rollout *v1alpha1.Rollout, allRSs []*appsv1.ReplicaSet, newRS *appsv1.ReplicaSet) (int32, error) { if rollout.Spec.Strategy.BlueGreen != nil { + desiredReplicas := defaults.GetReplicasOrDefault(rollout.Spec.Replicas) if rollout.Spec.Strategy.BlueGreen.PreviewReplicaCount != nil { activeRS, _ := GetReplicaSetByTemplateHash(allRSs, rollout.Status.BlueGreen.ActiveSelector) if activeRS == nil || activeRS.Name == newRS.Name { - return defaults.GetReplicasOrDefault(rollout.Spec.Replicas), nil + // the active RS is our desired RS. we are already past the blue-green promote step + return desiredReplicas, nil + } + if rollout.Status.PromoteFull { + // we are doing a full promotion. ignore previewReplicaCount + return desiredReplicas, nil } if newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] != rollout.Status.CurrentPodHash { + // the desired RS is not equal to our previously recorded current RS. + // This must be a new update, so return previewReplicaCount return *rollout.Spec.Strategy.BlueGreen.PreviewReplicaCount, nil } isNotPaused := !rollout.Spec.Paused && len(rollout.Status.PauseConditions) == 0 if isNotPaused && rollout.Status.BlueGreen.ScaleUpPreviewCheckPoint { - return defaults.GetReplicasOrDefault(rollout.Spec.Replicas), nil + // We are not paused, but we are already past our preview scale up checkpoint. + // If we get here, we were resumed after the pause, but haven't yet flipped the + // active service switch to the desired RS. + return desiredReplicas, nil } return *rollout.Spec.Strategy.BlueGreen.PreviewReplicaCount, nil } - - return defaults.GetReplicasOrDefault(rollout.Spec.Replicas), nil + return desiredReplicas, nil } if rollout.Spec.Strategy.Canary != nil { stableRS := GetStableRS(rollout, newRS, allRSs) diff --git a/utils/replicaset/replicaset_test.go b/utils/replicaset/replicaset_test.go index 62b9c7b0da..09165dd113 100644 --- a/utils/replicaset/replicaset_test.go +++ b/utils/replicaset/replicaset_test.go @@ -225,6 +225,7 @@ func TestNewRSNewReplicasWitPreviewReplicaCount(t *testing.T) { overrideCurrentPodHash string scaleUpPreviewCheckpoint bool expectReplicaCount int32 + promoteFull bool }{ { name: "No active rs is set", @@ -253,6 +254,12 @@ func TestNewRSNewReplicasWitPreviewReplicaCount(t *testing.T) { activeSelector: "bar", expectReplicaCount: previewReplicaCount, }, + { + name: "Ignore preview replica count during promote full", + activeSelector: "bar", + expectReplicaCount: replicaCount, + promoteFull: true, + }, } for i := range tests { test := tests[i] @@ -272,6 +279,7 @@ func TestNewRSNewReplicasWitPreviewReplicaCount(t *testing.T) { ActiveSelector: test.activeSelector, }, CurrentPodHash: "foo", + PromoteFull: test.promoteFull, }, } if test.overrideCurrentPodHash != "" { From 2600c6a368b193a74acd67348b0df36eb4f7a427 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Mon, 2 Aug 2021 16:37:36 -0700 Subject: [PATCH 03/34] chore: github release action was using incorect docker cache (#1387) Signed-off-by: Jesse Suen --- .github/workflows/release.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d80a2b406b..f146ba475c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,6 +17,10 @@ jobs: with: ref: ${{ github.event.inputs.tag }} + - name: Get SHA + id: get-sha + run: echo "::set-output name=sha::$(git log -1 --format='%H')" + - name: Set up QEMU uses: docker/setup-qemu-action@v1 @@ -27,9 +31,7 @@ jobs: uses: actions/cache@v2 with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- + key: ${{ runner.os }}-buildx-${{ steps.get-sha.outputs.sha }} - name: Print Disk Usage run: | From 9e23f78642262c3ce1637b9fcb83138a03d66d31 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Mon, 2 Aug 2021 19:38:09 -0700 Subject: [PATCH 04/34] chore: release workflow docker build context should use local path and not git context (#1388) Signed-off-by: Jesse Suen --- .github/workflows/release.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f146ba475c..0583bf14eb 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -47,6 +47,8 @@ jobs: ghcr.io/argoproj/argo-rollouts tags: | type=semver,pattern={{version}},prefix=v,value=${{ github.event.inputs.tag }} + flavor: | + latest=false - name: Docker meta (plugin) id: plugin-meta @@ -57,6 +59,8 @@ jobs: ghcr.io/argoproj/kubectl-argo-rollouts tags: | type=semver,pattern={{version}},prefix=v,value=${{ github.event.inputs.tag }} + flavor: | + latest=false - name: Login to GitHub Container Registry if: github.event_name != 'pull_request' @@ -77,6 +81,7 @@ jobs: - name: Build and push (controller-image) uses: docker/build-push-action@v2 with: + context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.controller-meta.outputs.tags }} @@ -86,6 +91,7 @@ jobs: - name: Build and push (plugin-image) uses: docker/build-push-action@v2 with: + context: . target: kubectl-argo-rollouts platforms: linux/amd64,linux/arm64 push: true From 2620490c9ab96e9da43b9c014fdf802c5e87dfa3 Mon Sep 17 00:00:00 2001 From: cskh Date: Tue, 3 Aug 2021 03:42:03 -0400 Subject: [PATCH 05/34] chore: Raname variables, import pkg for clarification (#1313) Signed-off-by: Hui Kang --- cmd/rollouts-controller/main.go | 4 ++-- controller/controller.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/rollouts-controller/main.go b/cmd/rollouts-controller/main.go index eb3d2e179f..67f08c2baf 100644 --- a/cmd/rollouts-controller/main.go +++ b/cmd/rollouts-controller/main.go @@ -99,7 +99,7 @@ func newCommand() *cobra.Command { kubeClient, err := kubernetes.NewForConfig(config) checkError(err) - rolloutClient, err := clientset.NewForConfig(config) + argoprojClient, err := clientset.NewForConfig(config) checkError(err) dynamicClient, err := dynamic.NewForConfig(config) checkError(err) @@ -150,7 +150,7 @@ func newCommand() *cobra.Command { cm := controller.NewManager( namespace, kubeClient, - rolloutClient, + argoprojClient, dynamicClient, smiClient, discoveryClient, diff --git a/controller/controller.go b/controller/controller.go index dadfb68ec0..47ca955851 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "github.com/argoproj/notifications-engine/pkg/api" - "github.com/argoproj/notifications-engine/pkg/controller" + notificationapi "github.com/argoproj/notifications-engine/pkg/api" + notificationcontroller "github.com/argoproj/notifications-engine/pkg/controller" "github.com/pkg/errors" smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" log "github.com/sirupsen/logrus" @@ -72,7 +72,7 @@ type Manager struct { analysisController *analysis.Controller serviceController *service.Controller ingressController *ingress.Controller - notificationsController controller.NotificationController + notificationsController notificationcontroller.NotificationController rolloutSynced cache.InformerSynced experimentSynced cache.InformerSynced @@ -148,10 +148,10 @@ func NewManager( ingressWorkqueue := workqueue.NewNamedRateLimitingQueue(queue.DefaultArgoRolloutsRateLimiter(), "Ingresses") refResolver := rollout.NewInformerBasedWorkloadRefResolver(namespace, dynamicclientset, discoveryClient, argoprojclientset, rolloutsInformer.Informer()) - apiFactory := api.NewFactory(record.NewAPIFactorySettings(), defaults.Namespace(), secretInformer.Informer(), configMapInformer.Informer()) + apiFactory := notificationapi.NewFactory(record.NewAPIFactorySettings(), defaults.Namespace(), secretInformer.Informer(), configMapInformer.Informer()) recorder := record.NewEventRecorder(kubeclientset, metrics.MetricRolloutEventsTotal, apiFactory) - notificationsController := controller.NewController(dynamicclientset.Resource(v1alpha1.RolloutGVR), rolloutsInformer.Informer(), apiFactory, - controller.WithToUnstructured(func(obj metav1.Object) (*unstructured.Unstructured, error) { + notificationsController := notificationcontroller.NewController(dynamicclientset.Resource(v1alpha1.RolloutGVR), rolloutsInformer.Informer(), apiFactory, + notificationcontroller.WithToUnstructured(func(obj metav1.Object) (*unstructured.Unstructured, error) { data, err := json.Marshal(obj) if err != nil { return nil, err From ffe70da2305161cb81876082c259f31d777d5789 Mon Sep 17 00:00:00 2001 From: Andrii Perenesenko Date: Wed, 4 Aug 2021 21:16:58 -0700 Subject: [PATCH 06/34] feat: configurable and more aggressive cleanup of old AnalysisRuns and Experiments (#1342) Signed-off-by: Andrii Perenesenko --- docs/features/specification.md | 8 + examples/analysis-templates.yaml | 3 +- manifests/crds/rollout-crd.yaml | 9 + manifests/install.yaml | 9 + manifests/namespace-install.yaml | 9 + pkg/apiclient/rollout/rollout.swagger.json | 20 + pkg/apis/rollouts/v1alpha1/generated.pb.go | 1089 ++++++++++------- pkg/apis/rollouts/v1alpha1/generated.proto | 13 + .../rollouts/v1alpha1/openapi_generated.go | 36 +- pkg/apis/rollouts/v1alpha1/types.go | 11 + .../v1alpha1/zz_generated.deepcopy.go | 31 + rollout/analysis.go | 5 +- rollout/experiment.go | 5 +- utils/analysis/filter.go | 37 +- utils/analysis/filter_test.go | 55 +- utils/defaults/defaults.go | 20 + utils/defaults/defaults_test.go | 28 + utils/experiment/filter.go | 24 +- utils/experiment/filter_test.go | 54 +- 19 files changed, 1029 insertions(+), 437 deletions(-) diff --git a/docs/features/specification.md b/docs/features/specification.md index 60acd6cb21..d719d74ca6 100644 --- a/docs/features/specification.md +++ b/docs/features/specification.md @@ -11,6 +11,14 @@ spec: # Number of desired pods. # Defaults to 1. replicas: 5 + analysis: + # limits the number of successful analysis runs and experiments to be stored in a history + # Defaults to 5. + successfulRunHistoryLimit: 10 + # limits the number of unsuccessful analysis runs and experiments to be stored in a history. + # Stages for unsuccessful: "Error", "Failed", "Inconclusive" + # Defaults to 5. + unsuccessfulRunHistoryLimit: 10 # Label selector for pods. Existing ReplicaSets whose pods are selected by # this will be the ones affected by this rollout. It must match the pod diff --git a/examples/analysis-templates.yaml b/examples/analysis-templates.yaml index 71ea53b659..84dde3b1c1 100644 --- a/examples/analysis-templates.yaml +++ b/examples/analysis-templates.yaml @@ -24,7 +24,7 @@ spec: args: [exit 0] restartPolicy: Never backoffLimit: 0 - + count: 1 --- # This AnalysisTemplate will run a Kubernetes Job every 5 seconds, with a 50% chance of failure. # When the number of accumulated failures exceeds failureLimit, it will cause the analysis run to @@ -36,6 +36,7 @@ metadata: spec: metrics: - name: random-fail + count: 2 interval: 5s failureLimit: 1 provider: diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index a54aaa4891..6c5cb0a944 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -47,6 +47,15 @@ spec: type: object spec: properties: + analysis: + properties: + successfulRunHistoryLimit: + format: int32 + type: integer + unsuccessfulRunHistoryLimit: + format: int32 + type: integer + type: object minReadySeconds: format: int32 type: integer diff --git a/manifests/install.yaml b/manifests/install.yaml index 84c413ad56..8c6b321ab2 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -9745,6 +9745,15 @@ spec: type: object spec: properties: + analysis: + properties: + successfulRunHistoryLimit: + format: int32 + type: integer + unsuccessfulRunHistoryLimit: + format: int32 + type: integer + type: object minReadySeconds: format: int32 type: integer diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 48f30b6761..2fc1caf4b0 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -9745,6 +9745,15 @@ spec: type: object spec: properties: + analysis: + properties: + successfulRunHistoryLimit: + format: int32 + type: integer + unsuccessfulRunHistoryLimit: + format: int32 + type: integer + type: object minReadySeconds: format: int32 type: integer diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 9e20548068..7657b2867c 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -550,6 +550,22 @@ }, "title": "AnalysisRunArgument argument to add to analysisRun" }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunStrategy": { + "type": "object", + "properties": { + "successfulRunHistoryLimit": { + "type": "integer", + "format": "int32", + "title": "SuccessfulRunHistoryLimit limits the number of old successful analysis runs and experiments to be retained in a history" + }, + "unsuccessfulRunHistoryLimit": { + "type": "integer", + "format": "int32", + "title": "UnsuccessfulRunHistoryLimit limits the number of old unsuccessful analysis runs and experiments to be retained in a history.\nStages for unsuccessful: \"Error\", \"Failed\", \"Inconclusive\"" + } + }, + "title": "AnalysisRunStrategy configuration for the analysis runs and experiments to retain" + }, "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AntiAffinity": { "type": "object", "properties": { @@ -1165,6 +1181,10 @@ "restartAt": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", "title": "RestartAt indicates when all the pods of a Rollout should be restarted" + }, + "analysis": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunStrategy", + "title": "Analysis configuration for the analysis runs to retain" } }, "title": "RolloutSpec is the spec for a Rollout resource" diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index 9004935887..471c0d18ed 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -244,10 +244,38 @@ func (m *AnalysisRunStatus) XXX_DiscardUnknown() { var xxx_messageInfo_AnalysisRunStatus proto.InternalMessageInfo +func (m *AnalysisRunStrategy) Reset() { *m = AnalysisRunStrategy{} } +func (*AnalysisRunStrategy) ProtoMessage() {} +func (*AnalysisRunStrategy) Descriptor() ([]byte, []int) { + return fileDescriptor_e0e705f843545fab, []int{7} +} +func (m *AnalysisRunStrategy) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AnalysisRunStrategy) 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 *AnalysisRunStrategy) XXX_Merge(src proto.Message) { + xxx_messageInfo_AnalysisRunStrategy.Merge(m, src) +} +func (m *AnalysisRunStrategy) XXX_Size() int { + return m.Size() +} +func (m *AnalysisRunStrategy) XXX_DiscardUnknown() { + xxx_messageInfo_AnalysisRunStrategy.DiscardUnknown(m) +} + +var xxx_messageInfo_AnalysisRunStrategy proto.InternalMessageInfo + func (m *AnalysisTemplate) Reset() { *m = AnalysisTemplate{} } func (*AnalysisTemplate) ProtoMessage() {} func (*AnalysisTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{7} + return fileDescriptor_e0e705f843545fab, []int{8} } func (m *AnalysisTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -275,7 +303,7 @@ var xxx_messageInfo_AnalysisTemplate proto.InternalMessageInfo func (m *AnalysisTemplateList) Reset() { *m = AnalysisTemplateList{} } func (*AnalysisTemplateList) ProtoMessage() {} func (*AnalysisTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{8} + return fileDescriptor_e0e705f843545fab, []int{9} } func (m *AnalysisTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -303,7 +331,7 @@ var xxx_messageInfo_AnalysisTemplateList proto.InternalMessageInfo func (m *AnalysisTemplateSpec) Reset() { *m = AnalysisTemplateSpec{} } func (*AnalysisTemplateSpec) ProtoMessage() {} func (*AnalysisTemplateSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{9} + return fileDescriptor_e0e705f843545fab, []int{10} } func (m *AnalysisTemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -331,7 +359,7 @@ var xxx_messageInfo_AnalysisTemplateSpec proto.InternalMessageInfo func (m *AntiAffinity) Reset() { *m = AntiAffinity{} } func (*AntiAffinity) ProtoMessage() {} func (*AntiAffinity) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{10} + return fileDescriptor_e0e705f843545fab, []int{11} } func (m *AntiAffinity) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -359,7 +387,7 @@ var xxx_messageInfo_AntiAffinity proto.InternalMessageInfo func (m *Argument) Reset() { *m = Argument{} } func (*Argument) ProtoMessage() {} func (*Argument) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{11} + return fileDescriptor_e0e705f843545fab, []int{12} } func (m *Argument) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -387,7 +415,7 @@ var xxx_messageInfo_Argument proto.InternalMessageInfo func (m *ArgumentValueFrom) Reset() { *m = ArgumentValueFrom{} } func (*ArgumentValueFrom) ProtoMessage() {} func (*ArgumentValueFrom) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{12} + return fileDescriptor_e0e705f843545fab, []int{13} } func (m *ArgumentValueFrom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -415,7 +443,7 @@ var xxx_messageInfo_ArgumentValueFrom proto.InternalMessageInfo func (m *BlueGreenStatus) Reset() { *m = BlueGreenStatus{} } func (*BlueGreenStatus) ProtoMessage() {} func (*BlueGreenStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{13} + return fileDescriptor_e0e705f843545fab, []int{14} } func (m *BlueGreenStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -443,7 +471,7 @@ var xxx_messageInfo_BlueGreenStatus proto.InternalMessageInfo func (m *BlueGreenStrategy) Reset() { *m = BlueGreenStrategy{} } func (*BlueGreenStrategy) ProtoMessage() {} func (*BlueGreenStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{14} + return fileDescriptor_e0e705f843545fab, []int{15} } func (m *BlueGreenStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -471,7 +499,7 @@ var xxx_messageInfo_BlueGreenStrategy proto.InternalMessageInfo func (m *CanaryStatus) Reset() { *m = CanaryStatus{} } func (*CanaryStatus) ProtoMessage() {} func (*CanaryStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{15} + return fileDescriptor_e0e705f843545fab, []int{16} } func (m *CanaryStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -499,7 +527,7 @@ var xxx_messageInfo_CanaryStatus proto.InternalMessageInfo func (m *CanaryStep) Reset() { *m = CanaryStep{} } func (*CanaryStep) ProtoMessage() {} func (*CanaryStep) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{16} + return fileDescriptor_e0e705f843545fab, []int{17} } func (m *CanaryStep) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -527,7 +555,7 @@ var xxx_messageInfo_CanaryStep proto.InternalMessageInfo func (m *CanaryStrategy) Reset() { *m = CanaryStrategy{} } func (*CanaryStrategy) ProtoMessage() {} func (*CanaryStrategy) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{17} + return fileDescriptor_e0e705f843545fab, []int{18} } func (m *CanaryStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -555,7 +583,7 @@ var xxx_messageInfo_CanaryStrategy proto.InternalMessageInfo func (m *ClusterAnalysisTemplate) Reset() { *m = ClusterAnalysisTemplate{} } func (*ClusterAnalysisTemplate) ProtoMessage() {} func (*ClusterAnalysisTemplate) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{18} + return fileDescriptor_e0e705f843545fab, []int{19} } func (m *ClusterAnalysisTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -583,7 +611,7 @@ var xxx_messageInfo_ClusterAnalysisTemplate proto.InternalMessageInfo func (m *ClusterAnalysisTemplateList) Reset() { *m = ClusterAnalysisTemplateList{} } func (*ClusterAnalysisTemplateList) ProtoMessage() {} func (*ClusterAnalysisTemplateList) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{19} + return fileDescriptor_e0e705f843545fab, []int{20} } func (m *ClusterAnalysisTemplateList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -611,7 +639,7 @@ var xxx_messageInfo_ClusterAnalysisTemplateList proto.InternalMessageInfo func (m *DatadogMetric) Reset() { *m = DatadogMetric{} } func (*DatadogMetric) ProtoMessage() {} func (*DatadogMetric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{20} + return fileDescriptor_e0e705f843545fab, []int{21} } func (m *DatadogMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -639,7 +667,7 @@ var xxx_messageInfo_DatadogMetric proto.InternalMessageInfo func (m *Experiment) Reset() { *m = Experiment{} } func (*Experiment) ProtoMessage() {} func (*Experiment) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{21} + return fileDescriptor_e0e705f843545fab, []int{22} } func (m *Experiment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -667,7 +695,7 @@ var xxx_messageInfo_Experiment proto.InternalMessageInfo func (m *ExperimentAnalysisRunStatus) Reset() { *m = ExperimentAnalysisRunStatus{} } func (*ExperimentAnalysisRunStatus) ProtoMessage() {} func (*ExperimentAnalysisRunStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{22} + return fileDescriptor_e0e705f843545fab, []int{23} } func (m *ExperimentAnalysisRunStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -695,7 +723,7 @@ var xxx_messageInfo_ExperimentAnalysisRunStatus proto.InternalMessageInfo func (m *ExperimentAnalysisTemplateRef) Reset() { *m = ExperimentAnalysisTemplateRef{} } func (*ExperimentAnalysisTemplateRef) ProtoMessage() {} func (*ExperimentAnalysisTemplateRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{23} + return fileDescriptor_e0e705f843545fab, []int{24} } func (m *ExperimentAnalysisTemplateRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -723,7 +751,7 @@ var xxx_messageInfo_ExperimentAnalysisTemplateRef proto.InternalMessageInfo func (m *ExperimentCondition) Reset() { *m = ExperimentCondition{} } func (*ExperimentCondition) ProtoMessage() {} func (*ExperimentCondition) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{24} + return fileDescriptor_e0e705f843545fab, []int{25} } func (m *ExperimentCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -751,7 +779,7 @@ var xxx_messageInfo_ExperimentCondition proto.InternalMessageInfo func (m *ExperimentList) Reset() { *m = ExperimentList{} } func (*ExperimentList) ProtoMessage() {} func (*ExperimentList) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{25} + return fileDescriptor_e0e705f843545fab, []int{26} } func (m *ExperimentList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -779,7 +807,7 @@ var xxx_messageInfo_ExperimentList proto.InternalMessageInfo func (m *ExperimentSpec) Reset() { *m = ExperimentSpec{} } func (*ExperimentSpec) ProtoMessage() {} func (*ExperimentSpec) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{26} + return fileDescriptor_e0e705f843545fab, []int{27} } func (m *ExperimentSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -807,7 +835,7 @@ var xxx_messageInfo_ExperimentSpec proto.InternalMessageInfo func (m *ExperimentStatus) Reset() { *m = ExperimentStatus{} } func (*ExperimentStatus) ProtoMessage() {} func (*ExperimentStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{27} + return fileDescriptor_e0e705f843545fab, []int{28} } func (m *ExperimentStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -835,7 +863,7 @@ var xxx_messageInfo_ExperimentStatus proto.InternalMessageInfo func (m *FieldRef) Reset() { *m = FieldRef{} } func (*FieldRef) ProtoMessage() {} func (*FieldRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{28} + return fileDescriptor_e0e705f843545fab, []int{29} } func (m *FieldRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -863,7 +891,7 @@ var xxx_messageInfo_FieldRef proto.InternalMessageInfo func (m *IstioDestinationRule) Reset() { *m = IstioDestinationRule{} } func (*IstioDestinationRule) ProtoMessage() {} func (*IstioDestinationRule) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{29} + return fileDescriptor_e0e705f843545fab, []int{30} } func (m *IstioDestinationRule) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -891,7 +919,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{30} + return fileDescriptor_e0e705f843545fab, []int{31} } func (m *IstioTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -919,7 +947,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{31} + return fileDescriptor_e0e705f843545fab, []int{32} } func (m *IstioVirtualService) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -947,7 +975,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{32} + return fileDescriptor_e0e705f843545fab, []int{33} } func (m *JobMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -975,7 +1003,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{33} + return fileDescriptor_e0e705f843545fab, []int{34} } func (m *KayentaMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1003,7 +1031,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{34} + return fileDescriptor_e0e705f843545fab, []int{35} } func (m *KayentaScope) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1031,7 +1059,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{35} + return fileDescriptor_e0e705f843545fab, []int{36} } func (m *KayentaThreshold) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1059,7 +1087,7 @@ var xxx_messageInfo_KayentaThreshold proto.InternalMessageInfo func (m *Measurement) Reset() { *m = Measurement{} } func (*Measurement) ProtoMessage() {} func (*Measurement) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{36} + return fileDescriptor_e0e705f843545fab, []int{37} } func (m *Measurement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1087,7 +1115,7 @@ var xxx_messageInfo_Measurement proto.InternalMessageInfo func (m *Metric) Reset() { *m = Metric{} } func (*Metric) ProtoMessage() {} func (*Metric) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{37} + return fileDescriptor_e0e705f843545fab, []int{38} } func (m *Metric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1115,7 +1143,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{38} + return fileDescriptor_e0e705f843545fab, []int{39} } func (m *MetricProvider) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1143,7 +1171,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{39} + return fileDescriptor_e0e705f843545fab, []int{40} } func (m *MetricResult) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1171,7 +1199,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{40} + return fileDescriptor_e0e705f843545fab, []int{41} } func (m *NewRelicMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1199,7 +1227,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{41} + return fileDescriptor_e0e705f843545fab, []int{42} } func (m *NginxTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1227,7 +1255,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{42} + return fileDescriptor_e0e705f843545fab, []int{43} } func (m *ObjectRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1255,7 +1283,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{43} + return fileDescriptor_e0e705f843545fab, []int{44} } func (m *PauseCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1283,7 +1311,7 @@ var xxx_messageInfo_PauseCondition proto.InternalMessageInfo func (m *PodTemplateMetadata) Reset() { *m = PodTemplateMetadata{} } func (*PodTemplateMetadata) ProtoMessage() {} func (*PodTemplateMetadata) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{44} + return fileDescriptor_e0e705f843545fab, []int{45} } func (m *PodTemplateMetadata) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1313,7 +1341,7 @@ func (m *PreferredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*PreferredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*PreferredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{45} + return fileDescriptor_e0e705f843545fab, []int{46} } func (m *PreferredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1341,7 +1369,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{46} + return fileDescriptor_e0e705f843545fab, []int{47} } func (m *PrometheusMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1371,7 +1399,7 @@ func (m *RequiredDuringSchedulingIgnoredDuringExecution) Reset() { } func (*RequiredDuringSchedulingIgnoredDuringExecution) ProtoMessage() {} func (*RequiredDuringSchedulingIgnoredDuringExecution) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{47} + return fileDescriptor_e0e705f843545fab, []int{48} } func (m *RequiredDuringSchedulingIgnoredDuringExecution) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1399,7 +1427,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{48} + return fileDescriptor_e0e705f843545fab, []int{49} } func (m *Rollout) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1427,7 +1455,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{49} + return fileDescriptor_e0e705f843545fab, []int{50} } func (m *RolloutAnalysis) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1455,7 +1483,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{50} + return fileDescriptor_e0e705f843545fab, []int{51} } func (m *RolloutAnalysisBackground) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1483,7 +1511,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{51} + return fileDescriptor_e0e705f843545fab, []int{52} } func (m *RolloutAnalysisRunStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1511,7 +1539,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{52} + return fileDescriptor_e0e705f843545fab, []int{53} } func (m *RolloutAnalysisTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1539,7 +1567,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{53} + return fileDescriptor_e0e705f843545fab, []int{54} } func (m *RolloutCondition) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1567,7 +1595,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{54} + return fileDescriptor_e0e705f843545fab, []int{55} } func (m *RolloutExperimentStep) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1597,7 +1625,7 @@ func (m *RolloutExperimentStepAnalysisTemplateRef) Reset() { } func (*RolloutExperimentStepAnalysisTemplateRef) ProtoMessage() {} func (*RolloutExperimentStepAnalysisTemplateRef) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{55} + return fileDescriptor_e0e705f843545fab, []int{56} } func (m *RolloutExperimentStepAnalysisTemplateRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1625,7 +1653,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{56} + return fileDescriptor_e0e705f843545fab, []int{57} } func (m *RolloutExperimentTemplate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1653,7 +1681,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{57} + return fileDescriptor_e0e705f843545fab, []int{58} } func (m *RolloutList) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1681,7 +1709,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{58} + return fileDescriptor_e0e705f843545fab, []int{59} } func (m *RolloutPause) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1709,7 +1737,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{59} + return fileDescriptor_e0e705f843545fab, []int{60} } func (m *RolloutSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1737,7 +1765,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{60} + return fileDescriptor_e0e705f843545fab, []int{61} } func (m *RolloutStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1765,7 +1793,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{61} + return fileDescriptor_e0e705f843545fab, []int{62} } func (m *RolloutStrategy) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1793,7 +1821,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{62} + return fileDescriptor_e0e705f843545fab, []int{63} } func (m *RolloutTrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1821,7 +1849,7 @@ var xxx_messageInfo_RolloutTrafficRouting proto.InternalMessageInfo func (m *SMITrafficRouting) Reset() { *m = SMITrafficRouting{} } func (*SMITrafficRouting) ProtoMessage() {} func (*SMITrafficRouting) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{63} + return fileDescriptor_e0e705f843545fab, []int{64} } func (m *SMITrafficRouting) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1849,7 +1877,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{64} + return fileDescriptor_e0e705f843545fab, []int{65} } func (m *ScopeDetail) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1877,7 +1905,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{65} + return fileDescriptor_e0e705f843545fab, []int{66} } func (m *SecretKeyRef) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1905,7 +1933,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{66} + return fileDescriptor_e0e705f843545fab, []int{67} } func (m *SetCanaryScale) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1933,7 +1961,7 @@ var xxx_messageInfo_SetCanaryScale proto.InternalMessageInfo func (m *TemplateService) Reset() { *m = TemplateService{} } func (*TemplateService) ProtoMessage() {} func (*TemplateService) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{67} + return fileDescriptor_e0e705f843545fab, []int{68} } func (m *TemplateService) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1961,7 +1989,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{68} + return fileDescriptor_e0e705f843545fab, []int{69} } func (m *TemplateSpec) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1989,7 +2017,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{69} + return fileDescriptor_e0e705f843545fab, []int{70} } func (m *TemplateStatus) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2017,7 +2045,7 @@ var xxx_messageInfo_TemplateStatus proto.InternalMessageInfo func (m *ValueFrom) Reset() { *m = ValueFrom{} } func (*ValueFrom) ProtoMessage() {} func (*ValueFrom) Descriptor() ([]byte, []int) { - return fileDescriptor_e0e705f843545fab, []int{70} + return fileDescriptor_e0e705f843545fab, []int{71} } func (m *ValueFrom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2045,7 +2073,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{71} + return fileDescriptor_e0e705f843545fab, []int{72} } func (m *WavefrontMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2073,7 +2101,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{72} + return fileDescriptor_e0e705f843545fab, []int{73} } func (m *WebMetric) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2101,7 +2129,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{73} + return fileDescriptor_e0e705f843545fab, []int{74} } func (m *WebMetricHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2134,6 +2162,7 @@ func init() { proto.RegisterType((*AnalysisRunList)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunList") proto.RegisterType((*AnalysisRunSpec)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunSpec") proto.RegisterType((*AnalysisRunStatus)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunStatus") + proto.RegisterType((*AnalysisRunStrategy)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisRunStrategy") proto.RegisterType((*AnalysisTemplate)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisTemplate") proto.RegisterType((*AnalysisTemplateList)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisTemplateList") proto.RegisterType((*AnalysisTemplateSpec)(nil), "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.AnalysisTemplateSpec") @@ -2212,367 +2241,371 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 5748 bytes of a gzipped FileDescriptorProto + // 5816 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x24, 0xd9, - 0x55, 0xf0, 0x56, 0xff, 0xd8, 0xed, 0xd3, 0xfe, 0xbd, 0xe3, 0xc9, 0x74, 0x66, 0x77, 0xdc, 0x93, - 0xda, 0x68, 0xbf, 0xc9, 0x47, 0x62, 0x27, 0xb3, 0xbb, 0xb0, 0x64, 0xa3, 0x15, 0xdd, 0xf6, 0xcc, - 0x8e, 0xbd, 0xf6, 0x8c, 0xe7, 0xb6, 0x67, 0x47, 0xd9, 0x64, 0x43, 0xca, 0xdd, 0xd7, 0xed, 0x9a, - 0xa9, 0xae, 0xea, 0x54, 0x55, 0x7b, 0xc6, 0x9b, 0x28, 0x3f, 0x44, 0xf9, 0x01, 0x25, 0xca, 0xf2, - 0xf3, 0x82, 0x10, 0x08, 0x21, 0x1e, 0x10, 0xbc, 0x20, 0x94, 0xc7, 0x44, 0x44, 0x40, 0xa4, 0x20, - 0x04, 0x0a, 0x2f, 0x6c, 0x40, 0x4a, 0x93, 0xed, 0x20, 0x21, 0x78, 0x41, 0x41, 0x91, 0x50, 0x46, - 0x42, 0x42, 0xf7, 0xa7, 0x6e, 0xd5, 0xad, 0xae, 0x6e, 0x77, 0xbb, 0xcb, 0x43, 0x04, 0xbc, 0xb9, - 0xef, 0x39, 0xf7, 0x9c, 0x7b, 0xeb, 0x9e, 0x7b, 0x7e, 0xee, 0x39, 0xf7, 0x1a, 0xb6, 0x9b, 0xa6, - 0x7f, 0xd8, 0xd9, 0x5f, 0xad, 0x3b, 0xad, 0x35, 0xc3, 0x6d, 0x3a, 0x6d, 0xd7, 0xb9, 0xc7, 0xfe, - 0x78, 0x9f, 0xeb, 0x58, 0x96, 0xd3, 0xf1, 0xbd, 0xb5, 0xf6, 0xfd, 0xe6, 0x9a, 0xd1, 0x36, 0xbd, - 0x35, 0xd9, 0x72, 0xf4, 0x01, 0xc3, 0x6a, 0x1f, 0x1a, 0x1f, 0x58, 0x6b, 0x12, 0x9b, 0xb8, 0x86, - 0x4f, 0x1a, 0xab, 0x6d, 0xd7, 0xf1, 0x1d, 0xf4, 0xa1, 0x90, 0xda, 0x6a, 0x40, 0x8d, 0xfd, 0xf1, - 0x8b, 0x41, 0xdf, 0xd5, 0xf6, 0xfd, 0xe6, 0x2a, 0xa5, 0xb6, 0x2a, 0x5b, 0x02, 0x6a, 0x17, 0xdf, - 0x17, 0x19, 0x4b, 0xd3, 0x69, 0x3a, 0x6b, 0x8c, 0xe8, 0x7e, 0xe7, 0x80, 0xfd, 0x62, 0x3f, 0xd8, - 0x5f, 0x9c, 0xd9, 0xc5, 0xa7, 0xef, 0xbf, 0xe0, 0xad, 0x9a, 0x0e, 0x1d, 0xdb, 0xda, 0xbe, 0xe1, - 0xd7, 0x0f, 0xd7, 0x8e, 0xfa, 0x46, 0x74, 0x51, 0x8f, 0x20, 0xd5, 0x1d, 0x97, 0x24, 0xe1, 0x3c, - 0x17, 0xe2, 0xb4, 0x8c, 0xfa, 0xa1, 0x69, 0x13, 0xf7, 0x38, 0x9c, 0x75, 0x8b, 0xf8, 0x46, 0x52, - 0xaf, 0xb5, 0x41, 0xbd, 0xdc, 0x8e, 0xed, 0x9b, 0x2d, 0xd2, 0xd7, 0xe1, 0x67, 0x4f, 0xea, 0xe0, - 0xd5, 0x0f, 0x49, 0xcb, 0xe8, 0xeb, 0xf7, 0xec, 0xa0, 0x7e, 0x1d, 0xdf, 0xb4, 0xd6, 0x4c, 0xdb, - 0xf7, 0x7c, 0x37, 0xde, 0x49, 0xff, 0x77, 0x0d, 0x96, 0x2a, 0xdb, 0xd5, 0x3d, 0xd7, 0x38, 0x38, - 0x30, 0xeb, 0xd8, 0xe9, 0xf8, 0xa6, 0xdd, 0x44, 0xef, 0x81, 0x69, 0xd3, 0x6e, 0xba, 0xc4, 0xf3, - 0x4a, 0xda, 0x65, 0xed, 0xca, 0x4c, 0x75, 0xe1, 0x3b, 0xdd, 0xf2, 0x13, 0xbd, 0x6e, 0x79, 0x7a, - 0x93, 0x37, 0xe3, 0x00, 0x8e, 0x9e, 0x87, 0xa2, 0x47, 0xdc, 0x23, 0xb3, 0x4e, 0x76, 0x1d, 0xd7, - 0x2f, 0x65, 0x2e, 0x6b, 0x57, 0xf2, 0xd5, 0x73, 0x02, 0xbd, 0x58, 0x0b, 0x41, 0x38, 0x8a, 0x47, - 0xbb, 0xb9, 0x8e, 0xe3, 0x0b, 0x78, 0x29, 0xcb, 0xb8, 0xc8, 0x6e, 0x38, 0x04, 0xe1, 0x28, 0x1e, - 0xda, 0x80, 0x45, 0xc3, 0xb6, 0x1d, 0xdf, 0xf0, 0x4d, 0xc7, 0xde, 0x75, 0xc9, 0x81, 0xf9, 0xb0, - 0x94, 0x63, 0x7d, 0x4b, 0xa2, 0xef, 0x62, 0x25, 0x06, 0xc7, 0x7d, 0x3d, 0xf4, 0x0d, 0x28, 0x55, - 0x5a, 0xfb, 0x86, 0xe7, 0x19, 0x0d, 0xc7, 0x8d, 0x4d, 0xfd, 0x0a, 0x14, 0x5a, 0x46, 0xbb, 0x6d, - 0xda, 0x4d, 0x3a, 0xf7, 0xec, 0x95, 0x99, 0xea, 0x6c, 0xaf, 0x5b, 0x2e, 0xec, 0x88, 0x36, 0x2c, - 0xa1, 0xfa, 0xdf, 0x67, 0xa0, 0x58, 0xb1, 0x0d, 0xeb, 0xd8, 0x33, 0x3d, 0xdc, 0xb1, 0xd1, 0xc7, - 0xa1, 0x40, 0x65, 0xa0, 0x61, 0xf8, 0x06, 0xfb, 0x6a, 0xc5, 0xab, 0xef, 0x5f, 0xe5, 0x4b, 0xb2, - 0x1a, 0x5d, 0x92, 0x50, 0xb2, 0x29, 0xf6, 0xea, 0xd1, 0x07, 0x56, 0x6f, 0xed, 0xdf, 0x23, 0x75, - 0x7f, 0x87, 0xf8, 0x46, 0x15, 0x89, 0x59, 0x40, 0xd8, 0x86, 0x25, 0x55, 0xe4, 0x40, 0xce, 0x6b, - 0x93, 0x3a, 0xfb, 0xc8, 0xc5, 0xab, 0x3b, 0xab, 0x93, 0xec, 0xa2, 0xd5, 0xc8, 0xd0, 0x6b, 0x6d, - 0x52, 0xaf, 0xce, 0x0a, 0xd6, 0x39, 0xfa, 0x0b, 0x33, 0x46, 0xe8, 0x01, 0x4c, 0x79, 0xbe, 0xe1, - 0x77, 0x3c, 0xb6, 0x40, 0xc5, 0xab, 0xb7, 0xd2, 0x63, 0xc9, 0xc8, 0x56, 0xe7, 0x05, 0xd3, 0x29, - 0xfe, 0x1b, 0x0b, 0x76, 0xfa, 0x3f, 0x68, 0x70, 0x2e, 0x82, 0x5d, 0x71, 0x9b, 0x9d, 0x16, 0xb1, - 0x7d, 0x74, 0x19, 0x72, 0xb6, 0xd1, 0x22, 0x42, 0x2a, 0xe5, 0x90, 0x6f, 0x1a, 0x2d, 0x82, 0x19, - 0x04, 0x3d, 0x0d, 0xf9, 0x23, 0xc3, 0xea, 0x10, 0xf6, 0x91, 0x66, 0xaa, 0x73, 0x02, 0x25, 0xff, - 0x2a, 0x6d, 0xc4, 0x1c, 0x86, 0x3e, 0x05, 0x33, 0xec, 0x8f, 0xeb, 0xae, 0xd3, 0x4a, 0x69, 0x6a, - 0x62, 0x84, 0xaf, 0x06, 0x64, 0xab, 0x73, 0xbd, 0x6e, 0x79, 0x46, 0xfe, 0xc4, 0x21, 0x43, 0xfd, - 0x1f, 0x35, 0x58, 0x88, 0x4c, 0x6e, 0xdb, 0xf4, 0x7c, 0xf4, 0xd1, 0x3e, 0xe1, 0x59, 0x1d, 0x4d, - 0x78, 0x68, 0x6f, 0x26, 0x3a, 0x8b, 0x62, 0xa6, 0x85, 0xa0, 0x25, 0x22, 0x38, 0x36, 0xe4, 0x4d, - 0x9f, 0xb4, 0xbc, 0x52, 0xe6, 0x72, 0xf6, 0x4a, 0xf1, 0xea, 0x66, 0x6a, 0xcb, 0x18, 0x7e, 0xdf, - 0x4d, 0x4a, 0x1f, 0x73, 0x36, 0xfa, 0x6f, 0x67, 0x94, 0x19, 0x52, 0x89, 0x42, 0x0e, 0x4c, 0xb7, - 0x88, 0xef, 0x9a, 0x75, 0xbe, 0xaf, 0x8a, 0x57, 0x37, 0x26, 0x1b, 0xc5, 0x0e, 0x23, 0x16, 0x6a, - 0x26, 0xfe, 0xdb, 0xc3, 0x01, 0x17, 0x74, 0x08, 0x39, 0xc3, 0x6d, 0x06, 0x73, 0xbe, 0x9e, 0xce, - 0xfa, 0x86, 0x32, 0x57, 0x71, 0x9b, 0x1e, 0x66, 0x1c, 0xd0, 0x1a, 0xcc, 0xf8, 0xc4, 0x6d, 0x99, - 0xb6, 0xe1, 0x73, 0x55, 0x56, 0xa8, 0x2e, 0x09, 0xb4, 0x99, 0xbd, 0x00, 0x80, 0x43, 0x1c, 0xfd, - 0xad, 0x0c, 0x2c, 0xf5, 0x6d, 0x06, 0xf4, 0x1c, 0xe4, 0xdb, 0x87, 0x86, 0x17, 0x48, 0xf7, 0x4a, - 0xf0, 0x69, 0x77, 0x69, 0xe3, 0xa3, 0x6e, 0x79, 0x2e, 0xe8, 0xc2, 0x1a, 0x30, 0x47, 0xa6, 0xba, - 0xba, 0x45, 0x3c, 0xcf, 0x68, 0x06, 0x22, 0x1f, 0xf9, 0x22, 0xac, 0x19, 0x07, 0x70, 0xf4, 0x25, - 0x0d, 0xe6, 0xf8, 0xd7, 0xc1, 0xc4, 0xeb, 0x58, 0x3e, 0xdd, 0xd6, 0xf4, 0xdb, 0x6c, 0xa5, 0xb1, - 0x12, 0x9c, 0x64, 0xf5, 0xbc, 0xe0, 0x3e, 0x17, 0x6d, 0xf5, 0xb0, 0xca, 0x17, 0xdd, 0x85, 0x19, - 0xcf, 0x37, 0x5c, 0x9f, 0x34, 0x2a, 0x3e, 0x53, 0xe0, 0xc5, 0xab, 0xff, 0x7f, 0x34, 0x79, 0xdf, - 0x33, 0x5b, 0x84, 0xef, 0xad, 0x5a, 0x40, 0x00, 0x87, 0xb4, 0xf4, 0x7f, 0xd5, 0x60, 0x31, 0xf8, - 0x4c, 0x7b, 0xa4, 0xd5, 0xb6, 0x0c, 0x9f, 0x3c, 0x06, 0xcd, 0xec, 0x2b, 0x9a, 0x19, 0xa7, 0xb3, - 0xbf, 0x82, 0xf1, 0x0f, 0x52, 0xcf, 0xfa, 0xbf, 0x68, 0xb0, 0x1c, 0x47, 0x7e, 0x0c, 0xda, 0xc4, - 0x53, 0xb5, 0xc9, 0xcd, 0x74, 0x67, 0x3b, 0x40, 0xa5, 0xfc, 0x28, 0x61, 0xae, 0xff, 0xc3, 0xf5, - 0x8a, 0xfe, 0x07, 0x39, 0x98, 0xad, 0xd8, 0xbe, 0x59, 0x39, 0x38, 0x30, 0x6d, 0xd3, 0x3f, 0x46, - 0x5f, 0xc9, 0xc0, 0x5a, 0xdb, 0x25, 0x07, 0xc4, 0x75, 0x49, 0x63, 0xa3, 0xe3, 0x9a, 0x76, 0xb3, - 0x56, 0x3f, 0x24, 0x8d, 0x8e, 0x65, 0xda, 0xcd, 0xcd, 0xa6, 0xed, 0xc8, 0xe6, 0x6b, 0x0f, 0x49, - 0xbd, 0x43, 0x5d, 0x1e, 0xb1, 0xfe, 0xad, 0xc9, 0x86, 0xb9, 0x3b, 0x1e, 0xd3, 0xea, 0xb3, 0xbd, - 0x6e, 0x79, 0x6d, 0xcc, 0x4e, 0x78, 0xdc, 0xa9, 0xa1, 0x2f, 0x67, 0x60, 0xd5, 0x25, 0x9f, 0xe8, - 0x98, 0xa3, 0x7f, 0x0d, 0xbe, 0x41, 0xad, 0xc9, 0xbe, 0x06, 0x1e, 0x8b, 0x67, 0xf5, 0x6a, 0xaf, - 0x5b, 0x1e, 0xb3, 0x0f, 0x1e, 0x73, 0x5e, 0xfa, 0x9f, 0x6b, 0x50, 0x18, 0xc3, 0x4b, 0x2a, 0xab, - 0x5e, 0xd2, 0x4c, 0x9f, 0x87, 0xe4, 0xf7, 0x7b, 0x48, 0x2f, 0x4f, 0xf6, 0xd1, 0x46, 0xf1, 0x8c, - 0xfe, 0x8d, 0x46, 0x23, 0x71, 0x4f, 0x0a, 0x1d, 0xc2, 0x72, 0xdb, 0x69, 0x04, 0x9b, 0xfe, 0x86, - 0xe1, 0x1d, 0x32, 0x98, 0x98, 0xde, 0x73, 0xbd, 0x6e, 0x79, 0x79, 0x37, 0x01, 0xfe, 0xa8, 0x5b, - 0x2e, 0x49, 0x22, 0x31, 0x04, 0x9c, 0x48, 0x11, 0xb5, 0xa1, 0x70, 0x60, 0x12, 0xab, 0x81, 0xc9, - 0x81, 0x90, 0x94, 0x09, 0xb7, 0xf7, 0x75, 0x41, 0x8d, 0x07, 0x11, 0xc1, 0x2f, 0x2c, 0xb9, 0xe8, - 0x3f, 0xc9, 0xc1, 0x42, 0xd5, 0xea, 0x90, 0x97, 0x5d, 0x42, 0x02, 0x3f, 0xa0, 0x02, 0x0b, 0x6d, - 0x97, 0x1c, 0x99, 0xe4, 0x41, 0x8d, 0x58, 0xa4, 0xee, 0x3b, 0xae, 0x98, 0xea, 0x05, 0xb1, 0x92, - 0x0b, 0xbb, 0x2a, 0x18, 0xc7, 0xf1, 0xd1, 0x4b, 0x30, 0x6f, 0xd4, 0x7d, 0xf3, 0x88, 0x48, 0x0a, - 0x7c, 0xa1, 0xdf, 0x21, 0x28, 0xcc, 0x57, 0x14, 0x28, 0x8e, 0x61, 0xa3, 0x8f, 0x42, 0xc9, 0xab, - 0x1b, 0x16, 0xb9, 0xd3, 0x16, 0xac, 0xd6, 0x0f, 0x49, 0xfd, 0xfe, 0xae, 0x63, 0xda, 0xbe, 0x70, - 0x70, 0x2e, 0x0b, 0x4a, 0xa5, 0xda, 0x00, 0x3c, 0x3c, 0x90, 0x02, 0xfa, 0x53, 0x0d, 0x2e, 0xb5, - 0x5d, 0xb2, 0xeb, 0x3a, 0x2d, 0x87, 0x4a, 0x6f, 0x9f, 0x2b, 0x24, 0x5c, 0x82, 0x57, 0x27, 0xdc, - 0xa6, 0xbc, 0xa5, 0x3f, 0xea, 0x78, 0x57, 0xaf, 0x5b, 0xbe, 0xb4, 0x3b, 0x6c, 0x00, 0x78, 0xf8, - 0xf8, 0xd0, 0x9f, 0x69, 0xb0, 0xd2, 0x76, 0x3c, 0x7f, 0xc8, 0x14, 0xf2, 0x67, 0x3a, 0x05, 0xbd, - 0xd7, 0x2d, 0xaf, 0xec, 0x0e, 0x1d, 0x01, 0x3e, 0x61, 0x84, 0x7a, 0xaf, 0x08, 0x4b, 0x11, 0xd9, - 0x73, 0x0d, 0x9f, 0x34, 0x8f, 0xd1, 0x8b, 0x30, 0x17, 0x08, 0x03, 0x8f, 0xcd, 0xb9, 0xec, 0x49, - 0xbf, 0xae, 0x12, 0x05, 0x62, 0x15, 0x97, 0xca, 0x9d, 0x14, 0x45, 0xde, 0x3b, 0x26, 0x77, 0xbb, - 0x0a, 0x14, 0xc7, 0xb0, 0xd1, 0x26, 0x9c, 0x13, 0x2d, 0x98, 0xb4, 0x2d, 0xb3, 0x6e, 0xac, 0x3b, - 0x1d, 0x21, 0x72, 0xf9, 0xea, 0x85, 0x5e, 0xb7, 0x7c, 0x6e, 0xb7, 0x1f, 0x8c, 0x93, 0xfa, 0xa0, - 0x6d, 0x58, 0x36, 0x3a, 0xbe, 0x23, 0xe7, 0x7f, 0xcd, 0x36, 0xf6, 0x2d, 0xd2, 0x60, 0xa2, 0x55, - 0xa8, 0x96, 0xa8, 0xd6, 0xa8, 0x24, 0xc0, 0x71, 0x62, 0x2f, 0xb4, 0x1b, 0xa3, 0x56, 0x23, 0x75, - 0xc7, 0x6e, 0xf0, 0x55, 0xce, 0x57, 0x9f, 0x12, 0xd3, 0x53, 0x29, 0x0a, 0x1c, 0x9c, 0xd8, 0x13, - 0x59, 0x30, 0xdf, 0x32, 0x1e, 0xde, 0xb1, 0x8d, 0x23, 0xc3, 0xb4, 0x28, 0x93, 0xd2, 0xd4, 0x09, - 0xae, 0x69, 0xc7, 0x37, 0xad, 0x55, 0x7e, 0x8e, 0xb3, 0xba, 0x69, 0xfb, 0xb7, 0xdc, 0x9a, 0x4f, - 0x8d, 0x40, 0x15, 0xd1, 0x0f, 0xbb, 0xa3, 0xd0, 0xc2, 0x31, 0xda, 0xe8, 0x16, 0x9c, 0x67, 0xdb, - 0x71, 0xc3, 0x79, 0x60, 0x6f, 0x10, 0xcb, 0x38, 0x0e, 0x26, 0x30, 0xcd, 0x26, 0xf0, 0xce, 0x5e, - 0xb7, 0x7c, 0xbe, 0x96, 0x84, 0x80, 0x93, 0xfb, 0x21, 0x03, 0x9e, 0x54, 0x01, 0x98, 0x1c, 0x99, - 0x9e, 0xe9, 0xd8, 0xdb, 0x66, 0xcb, 0xf4, 0x4b, 0x05, 0x46, 0xb6, 0xdc, 0xeb, 0x96, 0x9f, 0xac, - 0x0d, 0x46, 0xc3, 0xc3, 0x68, 0xa0, 0xdf, 0xd2, 0x60, 0x39, 0x69, 0x1b, 0x96, 0x66, 0xd2, 0x38, - 0xff, 0x88, 0x6d, 0x2d, 0x2e, 0x11, 0x89, 0x4a, 0x21, 0x71, 0x10, 0xe8, 0xb3, 0x1a, 0xcc, 0x1a, - 0x11, 0xe7, 0xac, 0x04, 0x6c, 0x54, 0x5b, 0x93, 0x7a, 0xc3, 0x21, 0xc5, 0xea, 0x62, 0xaf, 0x5b, - 0x56, 0x1c, 0x40, 0xac, 0x70, 0x44, 0xbf, 0xa3, 0xc1, 0xf9, 0xc4, 0x3d, 0x5e, 0x2a, 0x9e, 0xc5, - 0x17, 0x62, 0x42, 0x92, 0xac, 0x73, 0x92, 0x87, 0x81, 0xde, 0xd4, 0xa4, 0x29, 0xdb, 0x09, 0xe2, - 0x91, 0x59, 0x36, 0xb4, 0xdb, 0x13, 0xfa, 0xa3, 0xa1, 0xf5, 0x0e, 0x08, 0x57, 0xcf, 0x45, 0x2c, - 0x63, 0xd0, 0x88, 0xe3, 0xec, 0xd1, 0x57, 0xb5, 0xc0, 0x34, 0xca, 0x11, 0xcd, 0x9d, 0xd5, 0x88, - 0x50, 0x68, 0x69, 0xe5, 0x80, 0x62, 0xcc, 0xd1, 0xc7, 0xe0, 0xa2, 0xb1, 0xef, 0xb8, 0x7e, 0xe2, - 0xe6, 0x2b, 0xcd, 0xb3, 0x6d, 0xb4, 0xd2, 0xeb, 0x96, 0x2f, 0x56, 0x06, 0x62, 0xe1, 0x21, 0x14, - 0xf4, 0x7f, 0xce, 0xc2, 0xec, 0xba, 0x61, 0x1b, 0xee, 0xb1, 0x30, 0x5d, 0xdf, 0xd0, 0xe0, 0xa9, - 0x7a, 0xc7, 0x75, 0x89, 0xed, 0xd7, 0x7c, 0xd2, 0xee, 0x37, 0x5c, 0xda, 0x99, 0x1a, 0xae, 0xcb, - 0xbd, 0x6e, 0xf9, 0xa9, 0xf5, 0x21, 0xfc, 0xf1, 0xd0, 0xd1, 0xa1, 0xbf, 0xd1, 0x40, 0x17, 0x08, - 0x55, 0xa3, 0x7e, 0xbf, 0xe9, 0x3a, 0x1d, 0xbb, 0xd1, 0x3f, 0x89, 0xcc, 0x99, 0x4e, 0xe2, 0x99, - 0x5e, 0xb7, 0xac, 0xaf, 0x9f, 0x38, 0x0a, 0x3c, 0xc2, 0x48, 0xd1, 0xcb, 0xb0, 0x24, 0xb0, 0xae, - 0x3d, 0x6c, 0x13, 0xd7, 0xa4, 0xbe, 0xaf, 0x38, 0x0f, 0x7f, 0xa7, 0x30, 0x2b, 0x4b, 0xeb, 0x71, - 0x04, 0xdc, 0xdf, 0x47, 0xff, 0xe3, 0x1c, 0x40, 0xb0, 0xd2, 0xa4, 0x8d, 0x7e, 0x06, 0x66, 0x3c, - 0xe2, 0xdf, 0x25, 0x66, 0xf3, 0xd0, 0x67, 0x6b, 0x9a, 0x17, 0xc7, 0x26, 0x41, 0x23, 0x0e, 0xe1, - 0xe8, 0x3e, 0xe4, 0xdb, 0x46, 0xc7, 0x23, 0xe2, 0xbb, 0x6d, 0xa5, 0xf2, 0xdd, 0x76, 0x29, 0x45, - 0x1e, 0x5b, 0xb0, 0x3f, 0x31, 0xe7, 0x81, 0x3e, 0xaf, 0x01, 0x10, 0x75, 0xae, 0xc5, 0xab, 0xb5, - 0x54, 0x58, 0x86, 0x9f, 0x83, 0x7e, 0x83, 0xea, 0x7c, 0xaf, 0x5b, 0x86, 0xc8, 0x57, 0x8b, 0xb0, - 0x45, 0x0f, 0xa0, 0x60, 0x04, 0xea, 0x32, 0x77, 0x16, 0xea, 0x92, 0xb9, 0xfc, 0x72, 0xbd, 0x25, - 0x33, 0xf4, 0x65, 0x0d, 0xe6, 0x3d, 0xe2, 0x8b, 0xa5, 0xa2, 0x9b, 0x56, 0xf8, 0x8a, 0xdb, 0x93, - 0xf1, 0xaf, 0x29, 0x34, 0xb9, 0xf2, 0x51, 0xdb, 0x70, 0x8c, 0xaf, 0xfe, 0x66, 0x11, 0xe6, 0x03, - 0x91, 0x09, 0xdd, 0xbf, 0x3a, 0x6f, 0x49, 0x76, 0xff, 0xd6, 0xa3, 0x40, 0xac, 0xe2, 0xd2, 0xce, - 0x9e, 0x4f, 0xfd, 0x0d, 0xd5, 0xfb, 0x93, 0x9d, 0x6b, 0x51, 0x20, 0x56, 0x71, 0x51, 0x0b, 0xf2, - 0x9e, 0x4f, 0xda, 0xc1, 0xa1, 0xe4, 0x8d, 0xc9, 0xbe, 0x46, 0xb8, 0x13, 0xc2, 0x03, 0x25, 0xfa, - 0xcb, 0xc3, 0x9c, 0x0b, 0xfa, 0x9a, 0x06, 0xf3, 0xbe, 0x92, 0xfb, 0x11, 0x62, 0x90, 0x8e, 0x24, - 0xaa, 0x69, 0x25, 0xbe, 0x1a, 0x6a, 0x1b, 0x8e, 0xb1, 0x4f, 0xf0, 0x08, 0xf3, 0x67, 0xe8, 0x11, - 0xbe, 0x06, 0x85, 0x96, 0xf1, 0xb0, 0xd6, 0x71, 0x9b, 0xa7, 0xf7, 0x3c, 0x45, 0x6a, 0x8c, 0x53, - 0xc1, 0x92, 0x1e, 0xfa, 0x9c, 0x16, 0xd9, 0x5c, 0xd3, 0x8c, 0xf8, 0xdd, 0x74, 0x37, 0x97, 0x54, - 0xa8, 0x03, 0xb7, 0x59, 0x9f, 0x7f, 0x56, 0x78, 0xec, 0xfe, 0x19, 0xf5, 0x35, 0xf8, 0x06, 0x91, - 0xbe, 0xc6, 0xcc, 0x99, 0xfa, 0x1a, 0xeb, 0x0a, 0x33, 0x1c, 0x63, 0xce, 0xc6, 0xc3, 0xf7, 0x9c, - 0x1c, 0x0f, 0x9c, 0xe9, 0x78, 0x6a, 0x0a, 0x33, 0x1c, 0x63, 0x3e, 0x38, 0x28, 0x29, 0x9e, 0x4d, - 0x50, 0x32, 0x9b, 0x42, 0x50, 0x32, 0xdc, 0x5f, 0x9b, 0x9b, 0xd8, 0x5f, 0xfb, 0x91, 0x06, 0x17, - 0xd6, 0xad, 0x8e, 0xe7, 0x13, 0xf7, 0x7f, 0x4d, 0x1e, 0xe3, 0x3f, 0x34, 0x78, 0x72, 0xc0, 0x9c, - 0x1f, 0x43, 0x3a, 0xe3, 0x0d, 0x35, 0x9d, 0x71, 0x67, 0x42, 0xbb, 0x93, 0x3c, 0x8f, 0x01, 0x59, - 0x0d, 0x1f, 0xe6, 0x36, 0x0c, 0xdf, 0x68, 0x38, 0x4d, 0x9e, 0x66, 0x40, 0x2f, 0x41, 0xc1, 0xb4, - 0x7d, 0xe2, 0x1e, 0x19, 0x96, 0xb0, 0xbc, 0x7a, 0x30, 0xf4, 0x4d, 0xd1, 0xfe, 0xa8, 0x5b, 0x9e, - 0xdf, 0xe8, 0xb8, 0xac, 0xa0, 0x81, 0xeb, 0x61, 0x2c, 0xfb, 0xa0, 0xa7, 0x21, 0xff, 0x89, 0x0e, - 0x71, 0x8f, 0xe3, 0xe9, 0xef, 0xdb, 0xb4, 0x11, 0x73, 0x98, 0xfe, 0x77, 0x19, 0x88, 0x78, 0x45, - 0x8f, 0x41, 0xac, 0x6c, 0x45, 0xac, 0x26, 0xf4, 0x73, 0x22, 0x3e, 0xde, 0xa0, 0xba, 0x85, 0xa3, - 0x58, 0xdd, 0xc2, 0xcd, 0xd4, 0x38, 0x0e, 0x2f, 0x5b, 0x78, 0x4b, 0x83, 0x27, 0x43, 0xe4, 0x7e, - 0x5f, 0xff, 0xe4, 0x83, 0xf9, 0xe7, 0xa1, 0x68, 0x84, 0xdd, 0xc4, 0x2a, 0xca, 0xba, 0x98, 0x08, - 0x45, 0x1c, 0xc5, 0x0b, 0x53, 0xc7, 0xd9, 0x53, 0xa6, 0x8e, 0x73, 0xc3, 0x53, 0xc7, 0xfa, 0x8f, - 0x33, 0x70, 0xa9, 0x7f, 0x66, 0x81, 0x74, 0x63, 0x72, 0x30, 0xc2, 0xdc, 0x5e, 0x80, 0x59, 0x5f, - 0x74, 0xa0, 0xad, 0x62, 0x72, 0xcb, 0x02, 0x73, 0x76, 0x2f, 0x02, 0xc3, 0x0a, 0x26, 0xed, 0x59, - 0xe7, 0xfb, 0xaa, 0x56, 0x77, 0xda, 0x41, 0x8e, 0x5d, 0xf6, 0x5c, 0x8f, 0xc0, 0xb0, 0x82, 0x29, - 0x93, 0x75, 0xb9, 0x33, 0x2f, 0x02, 0xa8, 0xc1, 0xf9, 0x20, 0x67, 0x73, 0xdd, 0x71, 0xd7, 0x9d, - 0x56, 0xdb, 0x22, 0x2c, 0xe5, 0x94, 0x67, 0x83, 0xbd, 0x24, 0xba, 0x9c, 0xc7, 0x49, 0x48, 0x38, - 0xb9, 0xaf, 0xfe, 0x56, 0x16, 0xce, 0x85, 0x9f, 0x7d, 0xdd, 0xb1, 0x1b, 0x26, 0xcb, 0x7c, 0xbd, - 0x08, 0x39, 0xff, 0xb8, 0x1d, 0x7c, 0xec, 0xff, 0x17, 0x0c, 0x67, 0xef, 0xb8, 0x4d, 0x57, 0xfb, - 0x42, 0x42, 0x17, 0x0a, 0xc2, 0xac, 0x13, 0xda, 0x96, 0xbb, 0x83, 0xaf, 0xc0, 0x73, 0xaa, 0x34, - 0x3f, 0xea, 0x96, 0x13, 0xaa, 0xe1, 0x56, 0x25, 0x25, 0x55, 0xe6, 0xd1, 0x3d, 0x98, 0xb7, 0x0c, - 0xcf, 0xbf, 0xd3, 0x6e, 0x18, 0x3e, 0xd9, 0x33, 0x5b, 0x44, 0xec, 0xb9, 0x71, 0xf2, 0xf9, 0xf2, - 0x78, 0x78, 0x5b, 0xa1, 0x84, 0x63, 0x94, 0xd1, 0x11, 0x20, 0xda, 0xb2, 0xe7, 0x1a, 0xb6, 0xc7, - 0x67, 0x45, 0xf9, 0x8d, 0x5f, 0x3f, 0x70, 0x51, 0xf0, 0x43, 0xdb, 0x7d, 0xd4, 0x70, 0x02, 0x07, - 0xf4, 0x0c, 0x4c, 0xb9, 0xc4, 0xf0, 0xc4, 0x62, 0xce, 0x84, 0xfb, 0x1f, 0xb3, 0x56, 0x2c, 0xa0, - 0xd1, 0x0d, 0x35, 0x75, 0xc2, 0x86, 0xfa, 0xbe, 0x06, 0xf3, 0xe1, 0x32, 0x3d, 0x06, 0x33, 0xd7, - 0x52, 0xcd, 0xdc, 0x8d, 0xb4, 0x54, 0xe2, 0x00, 0xcb, 0xf6, 0x27, 0xb9, 0xe8, 0xfc, 0x58, 0xa6, - 0xfe, 0x93, 0x30, 0x13, 0xec, 0xea, 0x20, 0x57, 0x3f, 0xa1, 0x37, 0xae, 0x78, 0x16, 0x91, 0x92, - 0x1b, 0xc1, 0x04, 0x87, 0xfc, 0xa8, 0x61, 0x6d, 0x08, 0xa3, 0x29, 0xc4, 0x5e, 0x1a, 0xd6, 0xc0, - 0x98, 0x26, 0x19, 0xd6, 0xa0, 0x0f, 0xba, 0x03, 0x17, 0xda, 0xae, 0xc3, 0x6a, 0x1e, 0x37, 0x88, - 0xd1, 0xb0, 0x4c, 0x9b, 0x04, 0x4e, 0x1f, 0xcf, 0x4e, 0x3c, 0xd9, 0xeb, 0x96, 0x2f, 0xec, 0x26, - 0xa3, 0xe0, 0x41, 0x7d, 0xd5, 0xd2, 0xa1, 0xdc, 0xc9, 0xa5, 0x43, 0xe8, 0x97, 0x65, 0x68, 0x45, - 0xbc, 0x52, 0x9e, 0x7d, 0xc4, 0x8f, 0xa4, 0xb5, 0x94, 0x09, 0x6a, 0x3d, 0x14, 0xa9, 0x8a, 0x60, - 0x8a, 0x25, 0xfb, 0xc1, 0xfe, 0xfb, 0xd4, 0xe9, 0xfc, 0x77, 0xfd, 0x0b, 0x79, 0x58, 0x8c, 0x1b, - 0xdb, 0xb3, 0x2f, 0x8b, 0xfa, 0x35, 0x0d, 0x16, 0x03, 0x41, 0xe1, 0x3c, 0x49, 0x70, 0x08, 0xb1, - 0x9d, 0x92, 0x7c, 0x72, 0xb7, 0x41, 0xd6, 0xa8, 0xee, 0xc5, 0xb8, 0xe1, 0x3e, 0xfe, 0xe8, 0x75, - 0x28, 0xca, 0x58, 0xfd, 0x54, 0x35, 0x52, 0x0b, 0xcc, 0x61, 0x08, 0x49, 0xe0, 0x28, 0x3d, 0xf4, - 0x05, 0x0d, 0xa0, 0x1e, 0x68, 0xf4, 0x40, 0x90, 0x6e, 0xa7, 0x25, 0x48, 0xd2, 0x56, 0x84, 0x7e, - 0xa1, 0x6c, 0xf2, 0x70, 0x84, 0x31, 0xfa, 0x75, 0x16, 0xa5, 0x4b, 0x47, 0x86, 0x8a, 0x0e, 0x1d, - 0xc9, 0x87, 0xd3, 0x16, 0xe9, 0xf0, 0xec, 0x56, 0x7a, 0x0d, 0x11, 0x90, 0x87, 0x95, 0x41, 0xe8, - 0x2f, 0x82, 0xcc, 0xd5, 0xd3, 0x1d, 0xca, 0xb2, 0xf5, 0xbb, 0x86, 0x7f, 0x28, 0x44, 0x50, 0xee, - 0xd0, 0xeb, 0x01, 0x00, 0x87, 0x38, 0xfa, 0x5f, 0x68, 0xb0, 0xbc, 0xe9, 0xf9, 0xa6, 0xb3, 0x41, - 0x3c, 0x9f, 0x6e, 0x5a, 0x6a, 0xdf, 0x3b, 0x16, 0x19, 0xc1, 0x43, 0xda, 0x80, 0x45, 0x71, 0xa0, - 0xd6, 0xd9, 0xf7, 0x88, 0x1f, 0xf1, 0x92, 0xa4, 0xe8, 0xac, 0xc7, 0xe0, 0xb8, 0xaf, 0x07, 0xa5, - 0x22, 0x4e, 0xd6, 0x42, 0x2a, 0x59, 0x95, 0x4a, 0x2d, 0x06, 0xc7, 0x7d, 0x3d, 0xf4, 0x6f, 0x66, - 0xe0, 0x1c, 0x9b, 0x46, 0xac, 0x40, 0xfa, 0x57, 0x35, 0x98, 0x3f, 0x32, 0x5d, 0xbf, 0x63, 0x58, - 0xd1, 0x23, 0xc2, 0x89, 0xa5, 0x87, 0xf1, 0x7a, 0x55, 0x21, 0x1c, 0xfa, 0x05, 0x6a, 0x3b, 0x8e, - 0x0d, 0x80, 0x8e, 0x69, 0xa1, 0xa1, 0x7e, 0xed, 0x74, 0x42, 0xd8, 0xa4, 0x75, 0xe4, 0x89, 0xa6, - 0x58, 0x23, 0x8e, 0xf3, 0xd7, 0x3f, 0x22, 0x3e, 0x9f, 0x3a, 0xf4, 0x11, 0x84, 0x40, 0x87, 0x29, - 0xd7, 0xe9, 0x50, 0x1b, 0x99, 0x61, 0xf5, 0xe7, 0xc0, 0x1c, 0x0d, 0xd6, 0x82, 0x05, 0x44, 0xff, - 0x23, 0x0d, 0x66, 0xb6, 0x9c, 0x7d, 0x11, 0x34, 0x7e, 0x2c, 0x85, 0x00, 0x4e, 0xea, 0x79, 0x79, - 0x5a, 0x13, 0xba, 0x0e, 0x2f, 0x29, 0xe1, 0xdb, 0x53, 0x11, 0xda, 0xab, 0xec, 0x42, 0x05, 0x25, - 0xb5, 0xe5, 0xec, 0x0f, 0x8c, 0xef, 0x7f, 0x2f, 0x0f, 0x73, 0xaf, 0x18, 0xc7, 0xc4, 0xf6, 0x0d, - 0x31, 0xe2, 0xf7, 0xc0, 0xb4, 0xd1, 0x68, 0x24, 0x5d, 0x30, 0xa8, 0xf0, 0x66, 0x1c, 0xc0, 0x59, - 0x44, 0xd4, 0x66, 0x79, 0xfd, 0x88, 0xed, 0x0e, 0x23, 0xa2, 0x10, 0x84, 0xa3, 0x78, 0xe1, 0x56, - 0x5a, 0x77, 0xec, 0x03, 0xb3, 0x99, 0xb4, 0x09, 0xd6, 0x63, 0x70, 0xdc, 0xd7, 0x03, 0x6d, 0x01, - 0x12, 0x65, 0x7f, 0x95, 0x7a, 0xdd, 0xe9, 0xd8, 0x7c, 0x33, 0xf1, 0x60, 0x49, 0x3a, 0x91, 0x3b, - 0x7d, 0x18, 0x38, 0xa1, 0x17, 0xfa, 0x28, 0x94, 0xea, 0x8c, 0xb2, 0x70, 0x29, 0xa2, 0x14, 0xb9, - 0x5b, 0x29, 0x6b, 0x6a, 0xd6, 0x07, 0xe0, 0xe1, 0x81, 0x14, 0xe8, 0x48, 0x3d, 0xdf, 0x71, 0x8d, - 0x26, 0x89, 0xd2, 0x9d, 0x52, 0x47, 0x5a, 0xeb, 0xc3, 0xc0, 0x09, 0xbd, 0xd0, 0x67, 0x60, 0xc6, - 0x3f, 0x74, 0x89, 0x77, 0xe8, 0x58, 0x0d, 0x71, 0x7c, 0x3b, 0x61, 0x04, 0x2d, 0x56, 0x7f, 0x2f, - 0xa0, 0x1a, 0x71, 0x72, 0x82, 0x26, 0x1c, 0xf2, 0x44, 0x2e, 0x4c, 0x79, 0x34, 0x7c, 0xf3, 0x4a, - 0x85, 0x34, 0xdc, 0x44, 0xc1, 0x9d, 0x45, 0x84, 0x91, 0xd8, 0x9d, 0x71, 0xc0, 0x82, 0x93, 0xfe, - 0xed, 0x0c, 0xcc, 0x46, 0x11, 0x47, 0xd8, 0xa9, 0x9f, 0xd7, 0x60, 0xb6, 0xee, 0xd8, 0xbe, 0xeb, - 0x58, 0x3c, 0x2e, 0xe5, 0x1b, 0x64, 0xc2, 0xf2, 0x7a, 0x46, 0x6a, 0x83, 0xf8, 0x86, 0x69, 0x45, - 0x42, 0xdc, 0x08, 0x1b, 0xac, 0x30, 0x45, 0x5f, 0xd1, 0x60, 0x21, 0xcc, 0x6b, 0x85, 0x01, 0x72, - 0xaa, 0x03, 0x91, 0xa5, 0x67, 0xd7, 0x54, 0x4e, 0x38, 0xce, 0x5a, 0xdf, 0x87, 0xc5, 0xf8, 0x6a, - 0xd3, 0x4f, 0xd9, 0x36, 0xc4, 0x5e, 0xcf, 0x86, 0x9f, 0x72, 0xd7, 0xf0, 0x3c, 0xcc, 0x20, 0xe8, - 0xbd, 0x50, 0x68, 0x19, 0x6e, 0xd3, 0xb4, 0x0d, 0x8b, 0x7d, 0xc5, 0x6c, 0x44, 0x21, 0x89, 0x76, - 0x2c, 0x31, 0xf4, 0x1f, 0xe6, 0xa0, 0xb8, 0x43, 0x0c, 0xaf, 0xe3, 0x12, 0x76, 0x82, 0x75, 0xe6, - 0x2e, 0xa2, 0x52, 0xaf, 0x9e, 0x4d, 0xaf, 0x5e, 0x1d, 0xbd, 0x06, 0x70, 0x60, 0xda, 0xa6, 0x77, - 0x78, 0xca, 0x4a, 0x78, 0x96, 0xe1, 0xbc, 0x2e, 0x29, 0xe0, 0x08, 0xb5, 0xf0, 0x2a, 0x4c, 0x7e, - 0xc8, 0x55, 0x98, 0x2f, 0x68, 0x11, 0xe3, 0xc1, 0x9d, 0xaf, 0xbb, 0x93, 0x16, 0x50, 0xcb, 0x85, - 0x59, 0x0d, 0x8c, 0xc9, 0x35, 0xdb, 0x77, 0x8f, 0x87, 0xda, 0x98, 0x3d, 0x28, 0xb8, 0xc4, 0xeb, - 0xb4, 0xa8, 0xb3, 0x3b, 0x3d, 0xf6, 0x67, 0x60, 0x49, 0x20, 0x2c, 0xfa, 0x63, 0x49, 0xe9, 0xe2, - 0x8b, 0x30, 0xa7, 0x0c, 0x01, 0x2d, 0x42, 0xf6, 0x3e, 0x39, 0xe6, 0x72, 0x82, 0xe9, 0x9f, 0x68, - 0x59, 0x29, 0x85, 0x15, 0x9f, 0xe5, 0x83, 0x99, 0x17, 0x34, 0xfd, 0xc7, 0x53, 0x30, 0x25, 0xec, - 0xd5, 0xc9, 0xba, 0x20, 0x7a, 0x70, 0x9b, 0x39, 0xc5, 0xc1, 0xed, 0x16, 0xcc, 0x9a, 0xb6, 0xe9, - 0x9b, 0x86, 0xc5, 0x22, 0x22, 0x61, 0xab, 0x9e, 0x09, 0xf6, 0xff, 0x66, 0x04, 0x96, 0x40, 0x47, - 0xe9, 0x8b, 0x6e, 0x43, 0x9e, 0x29, 0x73, 0x21, 0x4f, 0xe3, 0xe7, 0xf5, 0x58, 0xce, 0x9e, 0xd7, - 0xd6, 0x71, 0x4a, 0xcc, 0xa7, 0xec, 0xd4, 0xeb, 0xc4, 0xf3, 0xa4, 0x23, 0x2f, 0xc4, 0x2a, 0xf4, - 0x29, 0x63, 0x70, 0xdc, 0xd7, 0x83, 0x52, 0x39, 0x30, 0x4c, 0xab, 0xe3, 0x92, 0x90, 0xca, 0x94, - 0x4a, 0xe5, 0x7a, 0x0c, 0x8e, 0xfb, 0x7a, 0xa0, 0x03, 0x98, 0x15, 0x6d, 0x3c, 0xad, 0x33, 0x7d, - 0xca, 0x59, 0xb2, 0xf4, 0xdd, 0xf5, 0x08, 0x25, 0xac, 0xd0, 0x45, 0x1d, 0x58, 0x32, 0xed, 0xba, - 0x63, 0xd7, 0xad, 0x8e, 0x67, 0x1e, 0x91, 0xb0, 0xb0, 0xed, 0x34, 0xcc, 0xce, 0xf7, 0xba, 0xe5, - 0xa5, 0xcd, 0x38, 0x39, 0xdc, 0xcf, 0x01, 0x7d, 0x4e, 0x83, 0xf3, 0x75, 0xc7, 0xf6, 0x58, 0x69, - 0xf7, 0x11, 0xb9, 0xe6, 0xba, 0x8e, 0xcb, 0x79, 0xcf, 0x9c, 0x92, 0x37, 0x0b, 0xc4, 0xd7, 0x93, - 0x48, 0xe2, 0x64, 0x4e, 0xe8, 0x0d, 0x28, 0xb4, 0x5d, 0xe7, 0xc8, 0x6c, 0x10, 0x57, 0xa4, 0x08, - 0xb7, 0xd3, 0xb8, 0x55, 0xb1, 0x2b, 0x68, 0x86, 0x9a, 0x20, 0x68, 0xc1, 0x92, 0x9f, 0xfe, 0xf5, - 0x29, 0x98, 0x57, 0xd1, 0xd1, 0xa7, 0x01, 0xda, 0xae, 0xd3, 0x22, 0xfe, 0x21, 0x91, 0x05, 0x4a, - 0x37, 0x27, 0xbd, 0xd1, 0x10, 0xd0, 0x13, 0x17, 0x3e, 0x98, 0x26, 0x0d, 0x5b, 0x71, 0x84, 0x23, - 0x72, 0x61, 0xfa, 0x3e, 0xb7, 0x69, 0xc2, 0xc4, 0xbf, 0x92, 0x8a, 0x43, 0x22, 0x38, 0x17, 0xa9, - 0xc9, 0x11, 0x4d, 0x38, 0x60, 0x84, 0xf6, 0x21, 0xfb, 0x80, 0xec, 0xa7, 0x53, 0x7b, 0x7f, 0x97, - 0x88, 0x50, 0xa1, 0x3a, 0xdd, 0xeb, 0x96, 0xb3, 0x77, 0xc9, 0x3e, 0xa6, 0xc4, 0xe9, 0xbc, 0x1a, - 0x3c, 0xfd, 0x24, 0x54, 0xc5, 0x84, 0xf3, 0x52, 0x72, 0x59, 0x7c, 0x5e, 0xa2, 0x09, 0x07, 0x8c, - 0xd0, 0x1b, 0x30, 0xf3, 0xc0, 0x38, 0x22, 0x07, 0xae, 0x63, 0xfb, 0xa2, 0xc0, 0x61, 0xc2, 0xc2, - 0x9b, 0xbb, 0x01, 0x39, 0xc1, 0x97, 0x59, 0x5b, 0xd9, 0x88, 0x43, 0x76, 0xe8, 0x08, 0x0a, 0x36, - 0x79, 0x80, 0x89, 0x65, 0xd6, 0x45, 0xcd, 0xc3, 0x84, 0x62, 0x7d, 0x53, 0x50, 0x13, 0x9c, 0x99, - 0x19, 0x0a, 0xda, 0xb0, 0xe4, 0x45, 0xd7, 0xf2, 0x9e, 0xb3, 0x2f, 0x14, 0xd5, 0x84, 0x6b, 0x29, - 0xc3, 0x3e, 0xbe, 0x96, 0x5b, 0xce, 0x3e, 0xa6, 0xc4, 0xf5, 0x6f, 0xe6, 0x60, 0x36, 0x7a, 0xe7, - 0x6e, 0x04, 0x9b, 0x25, 0xdd, 0xa6, 0xcc, 0x38, 0x6e, 0x13, 0xf5, 0x7a, 0x5b, 0xa1, 0x8d, 0x0f, - 0x8e, 0xca, 0x36, 0x53, 0xf3, 0x1a, 0x42, 0xaf, 0x37, 0xd2, 0xe8, 0x61, 0x85, 0xe9, 0x18, 0xb9, - 0x2b, 0xea, 0x07, 0x71, 0x73, 0xc8, 0x8b, 0xb5, 0xa5, 0x1f, 0xa4, 0x18, 0xb8, 0xab, 0x00, 0xc2, - 0x5c, 0x1d, 0x74, 0x2c, 0x71, 0x80, 0x29, 0x0f, 0xaf, 0x6a, 0x12, 0x82, 0x23, 0x58, 0xe8, 0x19, - 0x98, 0xa2, 0x06, 0x83, 0x34, 0x44, 0x15, 0xb5, 0x0c, 0x2d, 0xae, 0xb3, 0x56, 0x2c, 0xa0, 0xe8, - 0x05, 0x6a, 0xdb, 0x43, 0x35, 0x2f, 0x8a, 0xa3, 0x97, 0x43, 0xdb, 0x1e, 0xc2, 0xb0, 0x82, 0x49, - 0x87, 0x4e, 0xa8, 0x56, 0x66, 0xaa, 0x3f, 0x32, 0x74, 0xa6, 0xaa, 0x31, 0x87, 0xb1, 0x50, 0x37, - 0xa6, 0xc5, 0x99, 0xd2, 0xce, 0x47, 0x42, 0xdd, 0x18, 0x1c, 0xf7, 0xf5, 0xd0, 0x3f, 0x0e, 0xf3, - 0xaa, 0x34, 0xd3, 0x4f, 0xdc, 0x76, 0x9d, 0x03, 0xd3, 0x22, 0xf1, 0x20, 0x7d, 0x97, 0x37, 0xe3, - 0x00, 0x3e, 0x5a, 0xda, 0xf9, 0x2f, 0xb3, 0x70, 0xee, 0x66, 0xd3, 0xb4, 0x1f, 0xc6, 0x4e, 0x94, - 0x92, 0x2e, 0xf5, 0x6b, 0xe3, 0x5e, 0xea, 0x0f, 0x6b, 0xcf, 0xc4, 0x13, 0x05, 0xc9, 0xb5, 0x67, - 0xc1, 0xfb, 0x05, 0x2a, 0x2e, 0xfa, 0xbe, 0x06, 0x4f, 0x19, 0x0d, 0xee, 0x5f, 0x18, 0x96, 0x68, - 0x0d, 0x99, 0x06, 0x32, 0xee, 0x4d, 0xa8, 0x2d, 0xfa, 0x27, 0xbf, 0x5a, 0x19, 0xc2, 0x95, 0x7b, - 0xcd, 0xef, 0x16, 0x33, 0x78, 0x6a, 0x18, 0x2a, 0x1e, 0x3a, 0xfc, 0x8b, 0xb7, 0xe0, 0x5d, 0x27, - 0x32, 0x1a, 0xcb, 0x37, 0xfe, 0xbc, 0x06, 0x33, 0xfc, 0xf4, 0x08, 0x93, 0x03, 0xba, 0x79, 0x8c, - 0xb6, 0xf9, 0x2a, 0x71, 0xbd, 0xe0, 0xc6, 0xe1, 0x4c, 0xb8, 0x79, 0x2a, 0xbb, 0x9b, 0x02, 0x82, - 0x23, 0x58, 0x54, 0x3d, 0xdd, 0x37, 0xed, 0x86, 0x58, 0x26, 0xa9, 0x9e, 0x5e, 0x31, 0xed, 0x06, - 0x66, 0x10, 0xa9, 0xc0, 0xb2, 0x83, 0x14, 0x98, 0xfe, 0xfb, 0x1a, 0xcc, 0xb3, 0xd2, 0xd2, 0xd0, - 0x39, 0x7c, 0x5e, 0xa6, 0xea, 0xf8, 0x30, 0x2e, 0xa9, 0xa9, 0xba, 0x47, 0xdd, 0x72, 0x91, 0x17, - 0xa3, 0xaa, 0x99, 0xbb, 0x8f, 0x88, 0x00, 0x8f, 0x25, 0x14, 0x33, 0x63, 0xc7, 0x1f, 0xf2, 0x38, - 0xa3, 0x16, 0x10, 0xc1, 0x21, 0x3d, 0xfd, 0xeb, 0x59, 0x38, 0x97, 0x50, 0x23, 0x45, 0x63, 0xaf, - 0x29, 0xcb, 0xd8, 0x27, 0x56, 0x90, 0x0e, 0x7b, 0x3d, 0xf5, 0x3a, 0xac, 0xd5, 0x6d, 0x46, 0x9f, - 0x4b, 0x92, 0xd4, 0x4f, 0xbc, 0x11, 0x0b, 0xe6, 0xe8, 0x37, 0x35, 0x28, 0x1a, 0x11, 0x61, 0xe7, - 0x19, 0xc2, 0xfd, 0xf4, 0x07, 0xd3, 0x27, 0xdb, 0x91, 0xca, 0x86, 0x50, 0x94, 0xa3, 0x63, 0xb9, - 0xf8, 0xf3, 0x50, 0x8c, 0x4c, 0x61, 0x1c, 0x19, 0xbd, 0xf8, 0x12, 0x2c, 0x4e, 0x24, 0xe3, 0x1f, - 0x86, 0x71, 0xaf, 0xb0, 0x52, 0x8b, 0xf0, 0x20, 0x5a, 0x71, 0x2d, 0xbf, 0xb8, 0x28, 0xb9, 0x16, - 0x50, 0x7d, 0x1f, 0x16, 0xe3, 0x0e, 0xe8, 0x38, 0x67, 0xa2, 0x23, 0xa9, 0xdb, 0xf7, 0xc3, 0x98, - 0x97, 0x4e, 0xf5, 0xbf, 0xce, 0xc0, 0xb4, 0x28, 0xb4, 0x7c, 0x0c, 0x45, 0x41, 0xf7, 0x95, 0x53, - 0xe5, 0xcd, 0x54, 0xea, 0x43, 0x07, 0x56, 0x04, 0x79, 0xb1, 0x8a, 0xa0, 0x57, 0xd2, 0x61, 0x37, - 0xbc, 0x1c, 0xe8, 0x6b, 0x19, 0x58, 0x88, 0x15, 0xae, 0xa2, 0x2f, 0x6a, 0xfd, 0x59, 0xf0, 0x3b, - 0xa9, 0xd6, 0xc6, 0xca, 0x92, 0xb3, 0xe1, 0x09, 0x71, 0x4f, 0xb9, 0xc6, 0x7e, 0x3b, 0xb5, 0x27, - 0x41, 0x86, 0xde, 0x68, 0xff, 0x27, 0x0d, 0xde, 0x39, 0xb0, 0x94, 0x97, 0x5d, 0x17, 0x72, 0x55, - 0xa8, 0x90, 0xbd, 0x94, 0x4b, 0xf3, 0xe5, 0x69, 0x66, 0xfc, 0x86, 0x47, 0x9c, 0x3d, 0x7a, 0x0e, - 0x66, 0x99, 0x1e, 0xa7, 0xdb, 0xc7, 0x27, 0x6d, 0xf1, 0xbe, 0x11, 0x3b, 0x39, 0xa8, 0x45, 0xda, - 0xb1, 0x82, 0xa5, 0xff, 0xae, 0x06, 0xa5, 0x41, 0x97, 0x47, 0x46, 0xf0, 0xcb, 0x7f, 0x2e, 0x56, - 0xa0, 0x53, 0xee, 0x2b, 0xd0, 0x89, 0x79, 0xe6, 0x41, 0x2d, 0x4e, 0xc4, 0x29, 0xce, 0x9e, 0x50, - 0x7f, 0xf2, 0x55, 0x0d, 0x2e, 0x0c, 0x10, 0x9c, 0xbe, 0x42, 0x2d, 0xed, 0xd4, 0x85, 0x5a, 0x99, - 0x51, 0x0b, 0xb5, 0xf4, 0xbf, 0xcd, 0xc2, 0xa2, 0x18, 0x4f, 0x68, 0xcc, 0x5f, 0x50, 0xca, 0x9c, - 0xde, 0x1d, 0x2b, 0x73, 0x5a, 0x8e, 0xe3, 0xff, 0x5f, 0x8d, 0xd3, 0x4f, 0x57, 0x8d, 0xd3, 0x4f, - 0x32, 0x70, 0x3e, 0xf1, 0x62, 0x0e, 0xfa, 0x72, 0x82, 0x16, 0xbc, 0x9b, 0xf2, 0x0d, 0xa0, 0x11, - 0xf5, 0xe0, 0xa4, 0x85, 0x41, 0xbf, 0x11, 0x2d, 0xc8, 0xe1, 0x61, 0xc2, 0xc1, 0x19, 0xdc, 0x65, - 0x1a, 0xb3, 0x36, 0x47, 0xff, 0x95, 0x2c, 0x5c, 0x19, 0x95, 0xd0, 0x4f, 0x69, 0xed, 0xa6, 0xa7, - 0xd4, 0x6e, 0x3e, 0x1e, 0x0b, 0x75, 0x36, 0x65, 0x9c, 0x5f, 0xca, 0x4a, 0xb3, 0xd7, 0x2f, 0x9f, - 0x23, 0x25, 0x17, 0xa6, 0xa9, 0x17, 0x13, 0x3c, 0x4b, 0x11, 0xaa, 0xc2, 0xe9, 0x1a, 0x6f, 0x7e, - 0xd4, 0x2d, 0x2f, 0x89, 0xdb, 0xef, 0x35, 0xe2, 0x8b, 0x46, 0x1c, 0x74, 0x42, 0x57, 0xa0, 0xe0, - 0x72, 0x68, 0x50, 0xad, 0x26, 0x12, 0x26, 0xbc, 0x0d, 0x4b, 0x28, 0xfa, 0x4c, 0xc4, 0xed, 0xcb, - 0x9d, 0xd5, 0xdd, 0x90, 0x61, 0x79, 0xa0, 0xd7, 0xa1, 0xe0, 0x05, 0x6f, 0x56, 0xf0, 0xd3, 0xc1, - 0x67, 0x47, 0x2c, 0x82, 0xa4, 0x51, 0x42, 0xf0, 0x80, 0x05, 0x9f, 0x9f, 0x7c, 0xde, 0x42, 0x92, - 0xd4, 0xdf, 0xd2, 0xa0, 0x28, 0x56, 0xe2, 0x31, 0xd4, 0x5c, 0xde, 0x53, 0x6b, 0x2e, 0xaf, 0xa5, - 0xa2, 0x17, 0x06, 0x14, 0x5c, 0xde, 0x83, 0xd9, 0xe8, 0xbd, 0x4b, 0xf4, 0x5a, 0x44, 0xaf, 0x69, - 0x93, 0xdc, 0xef, 0x0a, 0x34, 0x5f, 0xa8, 0xf3, 0xf4, 0xbf, 0x9a, 0x92, 0x5f, 0x91, 0x55, 0x76, - 0x46, 0xe5, 0x4b, 0x1b, 0x2a, 0x5f, 0xd1, 0xe5, 0xcd, 0xa4, 0xbe, 0xbc, 0xe8, 0x36, 0x14, 0x02, - 0xe5, 0x23, 0x4c, 0xf4, 0xd3, 0xd1, 0x6a, 0x15, 0x6a, 0xe7, 0x29, 0xb1, 0x88, 0x50, 0xb2, 0x88, - 0x41, 0xae, 0xa1, 0x54, 0x8a, 0x92, 0x0c, 0x7a, 0x03, 0x8a, 0x0f, 0x1c, 0xf7, 0xbe, 0xe5, 0x18, - 0xec, 0x59, 0x18, 0x48, 0xe3, 0x0c, 0x57, 0x9e, 0x9c, 0xf0, 0x2a, 0xbd, 0xbb, 0x21, 0x7d, 0x1c, - 0x65, 0x86, 0x2a, 0xb0, 0xd0, 0x32, 0x6d, 0x4c, 0x8c, 0x86, 0x2c, 0xad, 0xcc, 0xf1, 0xa7, 0x30, - 0x02, 0x07, 0x76, 0x47, 0x05, 0xe3, 0x38, 0x3e, 0xfa, 0x24, 0x14, 0x3c, 0x71, 0xb7, 0x33, 0x9d, - 0xd3, 0x76, 0x19, 0xfa, 0x70, 0xa2, 0xe1, 0xb7, 0x0b, 0x5a, 0xb0, 0x64, 0x88, 0xb6, 0x61, 0xd9, - 0x15, 0xb7, 0xa7, 0x6e, 0x98, 0x9e, 0xef, 0xb8, 0xc7, 0x3c, 0x91, 0xc5, 0x8f, 0x57, 0xd9, 0x8b, - 0x0b, 0x38, 0x01, 0x8e, 0x13, 0x7b, 0x51, 0x0f, 0x85, 0x5d, 0x20, 0xe6, 0xc7, 0xad, 0x85, 0xd0, - 0x43, 0x61, 0x02, 0xdf, 0xc0, 0x02, 0x3a, 0xac, 0x54, 0xb7, 0x30, 0x41, 0xa9, 0xee, 0x5d, 0x98, - 0x71, 0x09, 0x73, 0xf3, 0x2b, 0x41, 0x2a, 0x6e, 0xec, 0x1a, 0x00, 0x1c, 0x10, 0xc0, 0x21, 0x2d, - 0xfd, 0x3f, 0xe7, 0x60, 0x4e, 0x09, 0x28, 0x69, 0x7c, 0xcf, 0xae, 0x88, 0xb1, 0xcd, 0x54, 0x08, - 0x37, 0x3c, 0xbb, 0x53, 0x86, 0x39, 0x0c, 0x7d, 0x4d, 0x83, 0x85, 0xb6, 0x72, 0xf8, 0x15, 0xe8, - 0x99, 0x09, 0x93, 0x1a, 0xea, 0x89, 0x5a, 0xe4, 0xd5, 0x21, 0x95, 0x19, 0x8e, 0x73, 0xa7, 0xe2, - 0x2a, 0x2a, 0x53, 0x2c, 0xe2, 0x32, 0x6c, 0x61, 0xed, 0x25, 0x89, 0x75, 0x15, 0x8c, 0xe3, 0xf8, - 0xf4, 0x23, 0xb3, 0xd9, 0x4d, 0xf2, 0x30, 0x60, 0x25, 0x20, 0x80, 0x43, 0x5a, 0xe8, 0x25, 0x98, - 0x17, 0x57, 0xe6, 0x77, 0x9d, 0xc6, 0x0d, 0xc3, 0x3b, 0x14, 0x6e, 0xae, 0x74, 0xcb, 0xd7, 0x15, - 0x28, 0x8e, 0x61, 0xb3, 0xb9, 0x85, 0xef, 0x12, 0x30, 0x02, 0x53, 0xea, 0xa3, 0x4c, 0xeb, 0x2a, - 0x18, 0xc7, 0xf1, 0xd1, 0x7b, 0x23, 0x5a, 0x92, 0x27, 0x0c, 0xe4, 0xde, 0x49, 0xd0, 0x94, 0x15, - 0x58, 0xe8, 0xb0, 0xa8, 0xa0, 0x11, 0x00, 0x85, 0xf4, 0x4a, 0x86, 0x77, 0x54, 0x30, 0x8e, 0xe3, - 0xa3, 0x17, 0x61, 0xce, 0xa5, 0xba, 0x40, 0x12, 0xe0, 0x59, 0x04, 0x79, 0x24, 0x8e, 0xa3, 0x40, - 0xac, 0xe2, 0xa2, 0x97, 0x61, 0x29, 0xbc, 0x3c, 0x1c, 0x10, 0xe0, 0x69, 0x05, 0xf9, 0x2e, 0x41, - 0x25, 0x8e, 0x80, 0xfb, 0xfb, 0xa0, 0x5f, 0x80, 0xc5, 0xc8, 0x97, 0xd8, 0xb4, 0x1b, 0xe4, 0xa1, - 0xb8, 0xe0, 0xb9, 0xcc, 0x52, 0x13, 0x31, 0x18, 0xee, 0xc3, 0x46, 0x1f, 0x84, 0xf9, 0xba, 0x63, - 0x59, 0x4c, 0x23, 0xf0, 0x07, 0x81, 0xf8, 0x4d, 0x4e, 0x7e, 0xe7, 0x55, 0x81, 0xe0, 0x18, 0x26, - 0xda, 0x02, 0xe4, 0xec, 0x7b, 0xc4, 0x3d, 0x22, 0x8d, 0x97, 0xf9, 0xdb, 0xc7, 0xd4, 0x20, 0xce, - 0xa9, 0x75, 0x71, 0xb7, 0xfa, 0x30, 0x70, 0x42, 0x2f, 0xb4, 0x0f, 0x17, 0x03, 0xed, 0xdc, 0xdf, - 0xa3, 0x54, 0x52, 0x82, 0x87, 0x8b, 0x77, 0x07, 0x62, 0xe2, 0x21, 0x54, 0xd0, 0x2f, 0xa9, 0x85, - 0xd9, 0xf3, 0x69, 0x3c, 0xb1, 0x18, 0x8f, 0x93, 0x4f, 0xac, 0xca, 0x76, 0x61, 0x8a, 0x97, 0x42, - 0x96, 0x16, 0xd2, 0xb8, 0x34, 0x1d, 0x7d, 0x7f, 0x24, 0xd4, 0xda, 0xbc, 0x15, 0x0b, 0x4e, 0xe8, - 0xd3, 0x30, 0xb3, 0x1f, 0x3c, 0x46, 0x55, 0x5a, 0x4c, 0xc3, 0x52, 0xc5, 0xde, 0x55, 0x0b, 0xe3, - 0x40, 0x09, 0xc0, 0x21, 0x4b, 0xf4, 0x0c, 0x14, 0x6f, 0xec, 0x56, 0xa4, 0xa4, 0x2f, 0x31, 0x09, - 0xcb, 0xd1, 0x2e, 0x38, 0x0a, 0xa0, 0xbb, 0x58, 0x7a, 0x30, 0x88, 0x2d, 0x79, 0x68, 0x01, 0xfb, - 0x1d, 0x12, 0x8a, 0xcd, 0x32, 0x4d, 0xb8, 0x56, 0x3a, 0x17, 0xc3, 0x16, 0xed, 0x58, 0x62, 0xa0, - 0xd7, 0xa1, 0x28, 0xcc, 0x02, 0xd3, 0x7f, 0xcb, 0xa7, 0x2b, 0xfa, 0xc7, 0x21, 0x09, 0x1c, 0xa5, - 0x87, 0x9e, 0x87, 0x62, 0x9b, 0xbd, 0xd1, 0x43, 0xae, 0x77, 0x2c, 0xab, 0x74, 0x9e, 0xe9, 0x66, - 0x79, 0x04, 0xbf, 0x1b, 0x82, 0x70, 0x14, 0x0f, 0x3d, 0x1b, 0xa4, 0x89, 0xdf, 0xa1, 0x64, 0x54, - 0x64, 0x9a, 0x58, 0xfa, 0x9d, 0x03, 0x8a, 0xeb, 0x2e, 0x9c, 0x70, 0x4c, 0xf0, 0xb9, 0xf0, 0x98, - 0x54, 0x3e, 0x43, 0xf1, 0xa9, 0xa8, 0x34, 0x68, 0x69, 0xbc, 0xd0, 0xdc, 0xf7, 0xd2, 0x19, 0x37, - 0x16, 0x89, 0xb2, 0xd0, 0x96, 0xf2, 0x9f, 0xca, 0x8d, 0x55, 0xf5, 0x89, 0x0d, 0x5e, 0xd0, 0xad, - 0x4a, 0xbf, 0xfe, 0x83, 0x9c, 0x3c, 0x2a, 0x89, 0x65, 0x47, 0x5d, 0xc8, 0x9b, 0x9e, 0x6f, 0x3a, - 0x29, 0x56, 0xd9, 0xc7, 0xde, 0xa6, 0x60, 0xd5, 0x5e, 0x0c, 0x80, 0x39, 0x2b, 0xca, 0xd3, 0x6e, - 0x9a, 0xf6, 0x43, 0x31, 0xfd, 0xdb, 0xa9, 0xa7, 0x3d, 0x39, 0x4f, 0x06, 0xc0, 0x9c, 0x15, 0xba, - 0x07, 0x59, 0xc3, 0xda, 0x4f, 0xe9, 0x35, 0xee, 0xf8, 0x8b, 0xf6, 0xbc, 0x56, 0xa2, 0xb2, 0x5d, - 0xc5, 0x94, 0x09, 0xe5, 0xe5, 0xb5, 0x4c, 0xe1, 0x5f, 0x4c, 0xc8, 0xab, 0xb6, 0xb3, 0x99, 0xc4, - 0xab, 0xb6, 0xb3, 0x89, 0x29, 0x13, 0xf4, 0x45, 0x0d, 0xc0, 0x90, 0xaf, 0xcd, 0xa7, 0xf3, 0x2c, - 0xe0, 0xa0, 0xd7, 0xeb, 0x79, 0x11, 0x53, 0x08, 0xc5, 0x11, 0xce, 0xfa, 0x9b, 0x1a, 0x2c, 0xf5, - 0x0d, 0x36, 0xfe, 0x10, 0xbf, 0x36, 0xfa, 0x43, 0xfc, 0xe2, 0xf5, 0x92, 0x5a, 0xdb, 0x32, 0x13, - 0x6f, 0xaa, 0xec, 0xc5, 0xe0, 0xb8, 0xaf, 0x87, 0xfe, 0x2d, 0x0d, 0x8a, 0x91, 0x2a, 0x63, 0xea, - 0xf7, 0xb2, 0x6a, 0x6c, 0x31, 0x8c, 0xf0, 0xe1, 0x16, 0x76, 0x3a, 0xc4, 0x61, 0xfc, 0xa0, 0xb2, - 0x19, 0x1e, 0xd7, 0x45, 0x0e, 0x2a, 0x69, 0x2b, 0x16, 0x50, 0x74, 0x19, 0x72, 0x9e, 0x4f, 0xda, - 0x4c, 0xa2, 0x22, 0x45, 0xc7, 0xec, 0xb8, 0x9e, 0x41, 0x18, 0x3b, 0xaa, 0x1c, 0x45, 0x05, 0x49, - 0xe4, 0x9d, 0x18, 0x83, 0xba, 0xd9, 0x0c, 0x86, 0x2e, 0x41, 0x96, 0xd8, 0x0d, 0xe1, 0x2d, 0x16, - 0x05, 0x4a, 0xf6, 0x9a, 0xdd, 0xc0, 0xb4, 0x5d, 0xbf, 0x05, 0xb3, 0x35, 0x52, 0x77, 0x89, 0xff, - 0x0a, 0x39, 0x1e, 0xed, 0x28, 0xed, 0x12, 0x4f, 0x41, 0x66, 0x54, 0x82, 0xb4, 0x3b, 0x6d, 0xd7, - 0xff, 0x50, 0x83, 0xd8, 0xb3, 0x3d, 0x48, 0x8f, 0x65, 0x15, 0xa1, 0x3f, 0xa3, 0xa8, 0x84, 0xe0, - 0x99, 0xa1, 0x21, 0xf8, 0x16, 0xa0, 0x96, 0xe1, 0xd7, 0x0f, 0xc5, 0xfa, 0x88, 0x17, 0xa2, 0xb8, - 0xa3, 0x1e, 0xde, 0x69, 0xe8, 0xc3, 0xc0, 0x09, 0xbd, 0xf4, 0x25, 0x58, 0x90, 0x81, 0x34, 0x97, - 0x0c, 0xfd, 0xdb, 0x59, 0x98, 0x55, 0x1e, 0x68, 0x3e, 0xf9, 0x8b, 0x8c, 0x3e, 0xf6, 0x84, 0x80, - 0x38, 0x3b, 0x66, 0x40, 0x1c, 0x3d, 0x81, 0xc8, 0x9d, 0xed, 0x09, 0x44, 0x3e, 0x9d, 0x13, 0x08, - 0x1f, 0xa6, 0xc5, 0xbf, 0xce, 0x10, 0x45, 0x6b, 0x3b, 0x29, 0xdd, 0x4a, 0x14, 0xb7, 0xac, 0x58, - 0x9d, 0x5e, 0xb0, 0xcb, 0x03, 0x56, 0xfa, 0x37, 0xf2, 0x30, 0xaf, 0xde, 0x53, 0x1c, 0x61, 0x25, - 0xdf, 0xdb, 0xb7, 0x92, 0x63, 0x86, 0x38, 0xd9, 0x49, 0x43, 0x9c, 0xdc, 0xa4, 0x21, 0x4e, 0xfe, - 0x14, 0x21, 0x4e, 0x7f, 0x80, 0x32, 0x35, 0x72, 0x80, 0xf2, 0x21, 0x99, 0xa9, 0x9a, 0x56, 0x8e, - 0x76, 0xc3, 0x4c, 0x15, 0x52, 0x97, 0x61, 0xdd, 0x69, 0x24, 0x66, 0xfc, 0x0a, 0x27, 0x94, 0xc1, - 0xb9, 0x89, 0x89, 0xa5, 0xf1, 0x0f, 0x32, 0xde, 0x31, 0x46, 0x52, 0x29, 0xfc, 0xef, 0x30, 0xcc, - 0x42, 0x80, 0x6a, 0x5d, 0x6a, 0x21, 0x08, 0x47, 0xf1, 0xd8, 0x0b, 0xc8, 0xea, 0xfb, 0xcc, 0x2c, - 0x62, 0x8c, 0xbe, 0x80, 0x1c, 0x7b, 0xcf, 0x39, 0x8e, 0xaf, 0x7f, 0x36, 0x03, 0xe1, 0x1b, 0xd3, - 0xec, 0x31, 0x28, 0x2f, 0xa2, 0xa6, 0x85, 0x33, 0xb5, 0x35, 0xe9, 0x8b, 0x6b, 0x21, 0x45, 0x91, - 0x13, 0x8e, 0xb4, 0x60, 0x85, 0xe3, 0x7f, 0xc3, 0xdb, 0xd2, 0x06, 0x2c, 0xc4, 0x4a, 0x63, 0x53, - 0xaf, 0x31, 0xf9, 0x56, 0x06, 0x66, 0x64, 0x71, 0x31, 0xb5, 0x6c, 0x1d, 0x37, 0x78, 0xb7, 0x46, - 0x5a, 0xb6, 0x3b, 0x78, 0x1b, 0xd3, 0x76, 0xf4, 0x10, 0xa6, 0x0f, 0x89, 0xd1, 0x20, 0x6e, 0x70, - 0x4e, 0xb5, 0x93, 0x52, 0x55, 0xf3, 0x0d, 0x46, 0x35, 0x9c, 0x0b, 0xff, 0xed, 0xe1, 0x80, 0x1d, - 0x7a, 0x09, 0xe6, 0x7d, 0xb3, 0x45, 0x68, 0x80, 0x11, 0xb1, 0x1a, 0xd9, 0xf0, 0xf0, 0x67, 0x4f, - 0x81, 0xe2, 0x18, 0x36, 0x55, 0x6b, 0xf7, 0x3c, 0xc7, 0x66, 0x57, 0x80, 0x73, 0x6a, 0x14, 0xb7, - 0x55, 0xbb, 0x75, 0x93, 0xdd, 0x00, 0x96, 0x18, 0x14, 0xdb, 0x64, 0xc5, 0x95, 0x2e, 0x11, 0x59, - 0xa3, 0xc5, 0xf0, 0x2a, 0x08, 0x6f, 0xc7, 0x12, 0x43, 0xbf, 0x03, 0x0b, 0xb1, 0x89, 0x04, 0x1e, - 0x82, 0x96, 0xec, 0x21, 0x8c, 0xf4, 0x2f, 0x6e, 0xaa, 0xab, 0xdf, 0x79, 0x7b, 0xe5, 0x89, 0xef, - 0xbe, 0xbd, 0xf2, 0xc4, 0xf7, 0xde, 0x5e, 0x79, 0xe2, 0xb3, 0xbd, 0x15, 0xed, 0x3b, 0xbd, 0x15, - 0xed, 0xbb, 0xbd, 0x15, 0xed, 0x7b, 0xbd, 0x15, 0xed, 0x07, 0xbd, 0x15, 0xed, 0xcd, 0x1f, 0xae, - 0x3c, 0xf1, 0x5a, 0x21, 0xf8, 0x98, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, 0x29, 0xaa, 0x1d, 0xb9, - 0xe1, 0x6b, 0x00, 0x00, + 0x55, 0xf0, 0x56, 0xff, 0xd8, 0xed, 0xd3, 0xfe, 0xbd, 0xe3, 0xc9, 0xf4, 0xce, 0xee, 0xb8, 0x27, + 0xb5, 0xd1, 0x7e, 0x93, 0x8f, 0xc4, 0x4e, 0x66, 0x77, 0x61, 0xc9, 0x46, 0x2b, 0xba, 0xed, 0x99, + 0x1d, 0x7b, 0xed, 0x19, 0xcf, 0x6d, 0xcf, 0x8e, 0xb2, 0x9b, 0x0d, 0x29, 0x77, 0x5f, 0xb7, 0x6b, + 0xa6, 0xba, 0xaa, 0x53, 0x55, 0xed, 0x19, 0x6f, 0xa2, 0xfc, 0x10, 0xe5, 0x07, 0x94, 0x28, 0xcb, + 0xcf, 0x0b, 0x42, 0x20, 0x84, 0x78, 0x40, 0xf0, 0x82, 0x50, 0x1e, 0x13, 0x11, 0x01, 0x91, 0x96, + 0x07, 0x50, 0x78, 0x80, 0x0d, 0x48, 0x69, 0x92, 0x0e, 0x12, 0x82, 0x17, 0x14, 0x14, 0x09, 0x65, + 0x25, 0x24, 0x74, 0x7f, 0xea, 0x56, 0xdd, 0xea, 0xea, 0x76, 0xb7, 0xbb, 0x3c, 0x44, 0xc0, 0x9b, + 0xfb, 0x9e, 0x73, 0xcf, 0xb9, 0xb7, 0xee, 0xb9, 0xe7, 0xe7, 0x9e, 0x73, 0xaf, 0x61, 0xbb, 0x69, + 0xfa, 0x87, 0x9d, 0xfd, 0xd5, 0xba, 0xd3, 0x5a, 0x33, 0xdc, 0xa6, 0xd3, 0x76, 0x9d, 0x7b, 0xec, + 0x8f, 0xf7, 0xbb, 0x8e, 0x65, 0x39, 0x1d, 0xdf, 0x5b, 0x6b, 0xdf, 0x6f, 0xae, 0x19, 0x6d, 0xd3, + 0x5b, 0x93, 0x2d, 0x47, 0x1f, 0x34, 0xac, 0xf6, 0xa1, 0xf1, 0xc1, 0xb5, 0x26, 0xb1, 0x89, 0x6b, + 0xf8, 0xa4, 0xb1, 0xda, 0x76, 0x1d, 0xdf, 0x41, 0x1f, 0x0e, 0xa9, 0xad, 0x06, 0xd4, 0xd8, 0x1f, + 0xbf, 0x18, 0xf4, 0x5d, 0x6d, 0xdf, 0x6f, 0xae, 0x52, 0x6a, 0xab, 0xb2, 0x25, 0xa0, 0x76, 0xf1, + 0xfd, 0x91, 0xb1, 0x34, 0x9d, 0xa6, 0xb3, 0xc6, 0x88, 0xee, 0x77, 0x0e, 0xd8, 0x2f, 0xf6, 0x83, + 0xfd, 0xc5, 0x99, 0x5d, 0x7c, 0xea, 0xfe, 0xf3, 0xde, 0xaa, 0xe9, 0xd0, 0xb1, 0xad, 0xed, 0x1b, + 0x7e, 0xfd, 0x70, 0xed, 0xa8, 0x6f, 0x44, 0x17, 0xf5, 0x08, 0x52, 0xdd, 0x71, 0x49, 0x12, 0xce, + 0xb3, 0x21, 0x4e, 0xcb, 0xa8, 0x1f, 0x9a, 0x36, 0x71, 0x8f, 0xc3, 0x59, 0xb7, 0x88, 0x6f, 0x24, + 0xf5, 0x5a, 0x1b, 0xd4, 0xcb, 0xed, 0xd8, 0xbe, 0xd9, 0x22, 0x7d, 0x1d, 0x7e, 0xf6, 0xa4, 0x0e, + 0x5e, 0xfd, 0x90, 0xb4, 0x8c, 0xbe, 0x7e, 0xcf, 0x0c, 0xea, 0xd7, 0xf1, 0x4d, 0x6b, 0xcd, 0xb4, + 0x7d, 0xcf, 0x77, 0xe3, 0x9d, 0xf4, 0x7f, 0xd7, 0x60, 0xa9, 0xb2, 0x5d, 0xdd, 0x73, 0x8d, 0x83, + 0x03, 0xb3, 0x8e, 0x9d, 0x8e, 0x6f, 0xda, 0x4d, 0xf4, 0x5e, 0x98, 0x36, 0xed, 0xa6, 0x4b, 0x3c, + 0xaf, 0xa4, 0x5d, 0xd6, 0xae, 0xcc, 0x54, 0x17, 0xde, 0xea, 0x96, 0x1f, 0xeb, 0x75, 0xcb, 0xd3, + 0x9b, 0xbc, 0x19, 0x07, 0x70, 0xf4, 0x1c, 0x14, 0x3d, 0xe2, 0x1e, 0x99, 0x75, 0xb2, 0xeb, 0xb8, + 0x7e, 0x29, 0x73, 0x59, 0xbb, 0x92, 0xaf, 0x9e, 0x13, 0xe8, 0xc5, 0x5a, 0x08, 0xc2, 0x51, 0x3c, + 0xda, 0xcd, 0x75, 0x1c, 0x5f, 0xc0, 0x4b, 0x59, 0xc6, 0x45, 0x76, 0xc3, 0x21, 0x08, 0x47, 0xf1, + 0xd0, 0x06, 0x2c, 0x1a, 0xb6, 0xed, 0xf8, 0x86, 0x6f, 0x3a, 0xf6, 0xae, 0x4b, 0x0e, 0xcc, 0x87, + 0xa5, 0x1c, 0xeb, 0x5b, 0x12, 0x7d, 0x17, 0x2b, 0x31, 0x38, 0xee, 0xeb, 0xa1, 0x6f, 0x40, 0xa9, + 0xd2, 0xda, 0x37, 0x3c, 0xcf, 0x68, 0x38, 0x6e, 0x6c, 0xea, 0x57, 0xa0, 0xd0, 0x32, 0xda, 0x6d, + 0xd3, 0x6e, 0xd2, 0xb9, 0x67, 0xaf, 0xcc, 0x54, 0x67, 0x7b, 0xdd, 0x72, 0x61, 0x47, 0xb4, 0x61, + 0x09, 0xd5, 0xff, 0x3e, 0x03, 0xc5, 0x8a, 0x6d, 0x58, 0xc7, 0x9e, 0xe9, 0xe1, 0x8e, 0x8d, 0x3e, + 0x0e, 0x05, 0x2a, 0x03, 0x0d, 0xc3, 0x37, 0xd8, 0x57, 0x2b, 0x5e, 0xfd, 0xc0, 0x2a, 0x5f, 0x92, + 0xd5, 0xe8, 0x92, 0x84, 0x92, 0x4d, 0xb1, 0x57, 0x8f, 0x3e, 0xb8, 0x7a, 0x6b, 0xff, 0x1e, 0xa9, + 0xfb, 0x3b, 0xc4, 0x37, 0xaa, 0x48, 0xcc, 0x02, 0xc2, 0x36, 0x2c, 0xa9, 0x22, 0x07, 0x72, 0x5e, + 0x9b, 0xd4, 0xd9, 0x47, 0x2e, 0x5e, 0xdd, 0x59, 0x9d, 0x64, 0x17, 0xad, 0x46, 0x86, 0x5e, 0x6b, + 0x93, 0x7a, 0x75, 0x56, 0xb0, 0xce, 0xd1, 0x5f, 0x98, 0x31, 0x42, 0x0f, 0x60, 0xca, 0xf3, 0x0d, + 0xbf, 0xe3, 0xb1, 0x05, 0x2a, 0x5e, 0xbd, 0x95, 0x1e, 0x4b, 0x46, 0xb6, 0x3a, 0x2f, 0x98, 0x4e, + 0xf1, 0xdf, 0x58, 0xb0, 0xd3, 0xff, 0x41, 0x83, 0x73, 0x11, 0xec, 0x8a, 0xdb, 0xec, 0xb4, 0x88, + 0xed, 0xa3, 0xcb, 0x90, 0xb3, 0x8d, 0x16, 0x11, 0x52, 0x29, 0x87, 0x7c, 0xd3, 0x68, 0x11, 0xcc, + 0x20, 0xe8, 0x29, 0xc8, 0x1f, 0x19, 0x56, 0x87, 0xb0, 0x8f, 0x34, 0x53, 0x9d, 0x13, 0x28, 0xf9, + 0x57, 0x68, 0x23, 0xe6, 0x30, 0xf4, 0x29, 0x98, 0x61, 0x7f, 0x5c, 0x77, 0x9d, 0x56, 0x4a, 0x53, + 0x13, 0x23, 0x7c, 0x25, 0x20, 0x5b, 0x9d, 0xeb, 0x75, 0xcb, 0x33, 0xf2, 0x27, 0x0e, 0x19, 0xea, + 0xff, 0xa8, 0xc1, 0x42, 0x64, 0x72, 0xdb, 0xa6, 0xe7, 0xa3, 0x8f, 0xf6, 0x09, 0xcf, 0xea, 0x68, + 0xc2, 0x43, 0x7b, 0x33, 0xd1, 0x59, 0x14, 0x33, 0x2d, 0x04, 0x2d, 0x11, 0xc1, 0xb1, 0x21, 0x6f, + 0xfa, 0xa4, 0xe5, 0x95, 0x32, 0x97, 0xb3, 0x57, 0x8a, 0x57, 0x37, 0x53, 0x5b, 0xc6, 0xf0, 0xfb, + 0x6e, 0x52, 0xfa, 0x98, 0xb3, 0xd1, 0x7f, 0x3b, 0xa3, 0xcc, 0x90, 0x4a, 0x14, 0x72, 0x60, 0xba, + 0x45, 0x7c, 0xd7, 0xac, 0xf3, 0x7d, 0x55, 0xbc, 0xba, 0x31, 0xd9, 0x28, 0x76, 0x18, 0xb1, 0x50, + 0x33, 0xf1, 0xdf, 0x1e, 0x0e, 0xb8, 0xa0, 0x43, 0xc8, 0x19, 0x6e, 0x33, 0x98, 0xf3, 0xf5, 0x74, + 0xd6, 0x37, 0x94, 0xb9, 0x8a, 0xdb, 0xf4, 0x30, 0xe3, 0x80, 0xd6, 0x60, 0xc6, 0x27, 0x6e, 0xcb, + 0xb4, 0x0d, 0x9f, 0xab, 0xb2, 0x42, 0x75, 0x49, 0xa0, 0xcd, 0xec, 0x05, 0x00, 0x1c, 0xe2, 0xe8, + 0x6f, 0x67, 0x60, 0xa9, 0x6f, 0x33, 0xa0, 0x67, 0x21, 0xdf, 0x3e, 0x34, 0xbc, 0x40, 0xba, 0x57, + 0x82, 0x4f, 0xbb, 0x4b, 0x1b, 0xdf, 0xe9, 0x96, 0xe7, 0x82, 0x2e, 0xac, 0x01, 0x73, 0x64, 0xaa, + 0xab, 0x5b, 0xc4, 0xf3, 0x8c, 0x66, 0x20, 0xf2, 0x91, 0x2f, 0xc2, 0x9a, 0x71, 0x00, 0x47, 0x5f, + 0xd2, 0x60, 0x8e, 0x7f, 0x1d, 0x4c, 0xbc, 0x8e, 0xe5, 0xd3, 0x6d, 0x4d, 0xbf, 0xcd, 0x56, 0x1a, + 0x2b, 0xc1, 0x49, 0x56, 0xcf, 0x0b, 0xee, 0x73, 0xd1, 0x56, 0x0f, 0xab, 0x7c, 0xd1, 0x5d, 0x98, + 0xf1, 0x7c, 0xc3, 0xf5, 0x49, 0xa3, 0xe2, 0x33, 0x05, 0x5e, 0xbc, 0xfa, 0xff, 0x47, 0x93, 0xf7, + 0x3d, 0xb3, 0x45, 0xf8, 0xde, 0xaa, 0x05, 0x04, 0x70, 0x48, 0x4b, 0xff, 0x5b, 0x55, 0x71, 0xd4, + 0x7c, 0x6a, 0xec, 0x9a, 0xc7, 0xe8, 0x35, 0x78, 0xdc, 0xeb, 0xd4, 0xeb, 0xc4, 0xf3, 0x0e, 0x3a, + 0x16, 0xee, 0xd8, 0x37, 0x4c, 0xcf, 0x77, 0xdc, 0xe3, 0x6d, 0xb3, 0x65, 0xfa, 0xec, 0x7b, 0xe7, + 0xab, 0x97, 0x7a, 0xdd, 0xf2, 0xe3, 0xb5, 0x41, 0x48, 0x78, 0x70, 0x7f, 0x64, 0xc0, 0x13, 0x1d, + 0x7b, 0x30, 0x79, 0x6e, 0x13, 0xcb, 0xbd, 0x6e, 0xf9, 0x89, 0x3b, 0x83, 0xd1, 0xf0, 0x30, 0x1a, + 0xfa, 0xbf, 0x6a, 0xb0, 0x18, 0xcc, 0x6b, 0x8f, 0xb4, 0xda, 0x96, 0xe1, 0x93, 0x47, 0x60, 0x71, + 0x7c, 0xc5, 0xe2, 0xe0, 0x74, 0xf4, 0x46, 0x30, 0xfe, 0x41, 0x66, 0x47, 0xff, 0x17, 0x0d, 0x96, + 0xe3, 0xc8, 0x8f, 0x40, 0x4b, 0x7a, 0xaa, 0x96, 0xbc, 0x99, 0xee, 0x6c, 0x07, 0xa8, 0xca, 0x1f, + 0x25, 0xcc, 0xf5, 0x7f, 0xb8, 0xbe, 0xd4, 0xff, 0x20, 0x07, 0xb3, 0x15, 0xdb, 0x37, 0x2b, 0x07, + 0x07, 0xa6, 0x6d, 0xfa, 0xc7, 0xe8, 0x2b, 0x19, 0x58, 0x6b, 0xbb, 0xe4, 0x80, 0xb8, 0x2e, 0x69, + 0x6c, 0x74, 0x5c, 0xd3, 0x6e, 0xd6, 0xea, 0x87, 0xa4, 0xd1, 0xb1, 0x4c, 0xbb, 0xb9, 0xd9, 0xb4, + 0x1d, 0xd9, 0x7c, 0xed, 0x21, 0xa9, 0x77, 0xa8, 0x2b, 0x27, 0xd6, 0xbf, 0x35, 0xd9, 0x30, 0x77, + 0xc7, 0x63, 0x5a, 0x7d, 0xa6, 0xd7, 0x2d, 0xaf, 0x8d, 0xd9, 0x09, 0x8f, 0x3b, 0x35, 0xf4, 0xe5, + 0x0c, 0xac, 0xba, 0xe4, 0x13, 0x1d, 0x73, 0xf4, 0xaf, 0xc1, 0x37, 0xa8, 0x35, 0xd9, 0xd7, 0xc0, + 0x63, 0xf1, 0xac, 0x5e, 0xed, 0x75, 0xcb, 0x63, 0xf6, 0xc1, 0x63, 0xce, 0x4b, 0xff, 0x73, 0x0d, + 0x0a, 0x63, 0x78, 0x7f, 0x65, 0xd5, 0xfb, 0x9b, 0xe9, 0xf3, 0xfc, 0xfc, 0x7e, 0xcf, 0xef, 0xa5, + 0xc9, 0x3e, 0xda, 0x28, 0x1e, 0xdf, 0xbf, 0xd1, 0x28, 0x2b, 0xee, 0x21, 0xa2, 0x43, 0x58, 0x6e, + 0x3b, 0x8d, 0x60, 0xd3, 0xdf, 0x30, 0xbc, 0x43, 0x06, 0x13, 0xd3, 0x7b, 0xb6, 0xd7, 0x2d, 0x2f, + 0xef, 0x26, 0xc0, 0xdf, 0xe9, 0x96, 0x4b, 0x92, 0x48, 0x0c, 0x01, 0x27, 0x52, 0x44, 0x6d, 0x28, + 0x1c, 0x98, 0xc4, 0x6a, 0x60, 0x72, 0x20, 0x24, 0x65, 0xc2, 0xed, 0x7d, 0x5d, 0x50, 0xe3, 0xc1, + 0x51, 0xf0, 0x0b, 0x4b, 0x2e, 0xfa, 0x4f, 0x72, 0xb0, 0x50, 0xb5, 0x3a, 0xe4, 0x25, 0x97, 0x90, + 0xc0, 0xbf, 0xa9, 0xc0, 0x42, 0xdb, 0x25, 0x47, 0x26, 0x79, 0x50, 0x23, 0x16, 0xa9, 0xfb, 0x8e, + 0x2b, 0xa6, 0x7a, 0x41, 0xac, 0xe4, 0xc2, 0xae, 0x0a, 0xc6, 0x71, 0x7c, 0xf4, 0x22, 0xcc, 0x1b, + 0x75, 0xdf, 0x3c, 0x22, 0x92, 0x02, 0x5f, 0xe8, 0x77, 0x09, 0x0a, 0xf3, 0x15, 0x05, 0x8a, 0x63, + 0xd8, 0xe8, 0xa3, 0x50, 0xf2, 0xea, 0x86, 0x45, 0xee, 0xb4, 0x05, 0xab, 0xf5, 0x43, 0x52, 0xbf, + 0xbf, 0xeb, 0x98, 0xb6, 0x2f, 0x1c, 0xb7, 0xcb, 0x82, 0x52, 0xa9, 0x36, 0x00, 0x0f, 0x0f, 0xa4, + 0x80, 0xfe, 0x54, 0x83, 0x4b, 0x6d, 0x97, 0xec, 0xba, 0x4e, 0xcb, 0xa1, 0xd2, 0xdb, 0xe7, 0xe2, + 0x09, 0x57, 0xe7, 0x95, 0x09, 0xb7, 0x29, 0x6f, 0xe9, 0x8f, 0xa6, 0xde, 0xdd, 0xeb, 0x96, 0x2f, + 0xed, 0x0e, 0x1b, 0x00, 0x1e, 0x3e, 0x3e, 0xf4, 0x67, 0x1a, 0xac, 0xb4, 0x1d, 0xcf, 0x1f, 0x32, + 0x85, 0xfc, 0x99, 0x4e, 0x41, 0xef, 0x75, 0xcb, 0x2b, 0xbb, 0x43, 0x47, 0x80, 0x4f, 0x18, 0xa1, + 0xde, 0x2b, 0xc2, 0x52, 0x44, 0xf6, 0x84, 0x07, 0xf8, 0x02, 0xcc, 0x05, 0xc2, 0xc0, 0xcf, 0x1c, + 0xb8, 0xec, 0x49, 0x7f, 0xb5, 0x12, 0x05, 0x62, 0x15, 0x97, 0xca, 0x9d, 0x14, 0x45, 0xde, 0x3b, + 0x26, 0x77, 0xbb, 0x0a, 0x14, 0xc7, 0xb0, 0xd1, 0x26, 0x9c, 0x13, 0x2d, 0x98, 0xb4, 0x2d, 0xb3, + 0x6e, 0xac, 0x3b, 0x1d, 0x21, 0x72, 0xf9, 0xea, 0x85, 0x5e, 0xb7, 0x7c, 0x6e, 0xb7, 0x1f, 0x8c, + 0x93, 0xfa, 0xa0, 0x6d, 0x58, 0x36, 0x3a, 0xbe, 0x23, 0xe7, 0x7f, 0xcd, 0x36, 0xf6, 0x2d, 0xd2, + 0x60, 0xa2, 0x55, 0xa8, 0x96, 0xa8, 0xd6, 0xa8, 0x24, 0xc0, 0x71, 0x62, 0x2f, 0xb4, 0x1b, 0xa3, + 0x56, 0x23, 0x75, 0xc7, 0x6e, 0xf0, 0x55, 0xce, 0x57, 0x9f, 0x14, 0xd3, 0x53, 0x29, 0x0a, 0x1c, + 0x9c, 0xd8, 0x13, 0x59, 0x30, 0xdf, 0x32, 0x1e, 0xde, 0xb1, 0x8d, 0x23, 0xc3, 0xb4, 0x28, 0x93, + 0xd2, 0xd4, 0x09, 0xae, 0x69, 0xc7, 0x37, 0xad, 0x55, 0x7e, 0x3e, 0xb5, 0xba, 0x69, 0xfb, 0xb7, + 0xdc, 0x9a, 0x4f, 0x8d, 0x40, 0x15, 0xd1, 0x0f, 0xbb, 0xa3, 0xd0, 0xc2, 0x31, 0xda, 0xe8, 0x16, + 0x9c, 0x67, 0xdb, 0x71, 0xc3, 0x79, 0x60, 0x6f, 0x10, 0xcb, 0x38, 0x0e, 0x26, 0x30, 0xcd, 0x26, + 0xf0, 0x78, 0xaf, 0x5b, 0x3e, 0x5f, 0x4b, 0x42, 0xc0, 0xc9, 0xfd, 0xa8, 0x2f, 0xaf, 0x02, 0x30, + 0x39, 0x32, 0x3d, 0xd3, 0xb1, 0xb9, 0x2f, 0x5f, 0x08, 0x7d, 0xf9, 0xda, 0x60, 0x34, 0x3c, 0x8c, + 0x06, 0xfa, 0x2d, 0x0d, 0x96, 0x93, 0xb6, 0x61, 0x69, 0x26, 0x8d, 0x73, 0x9d, 0xd8, 0xd6, 0xe2, + 0x12, 0x91, 0xa8, 0x14, 0x12, 0x07, 0x81, 0x3e, 0xab, 0xc1, 0xac, 0x11, 0x71, 0xce, 0x4a, 0xc0, + 0x46, 0xb5, 0x35, 0xa9, 0x37, 0x1c, 0x52, 0xac, 0x2e, 0xf6, 0xba, 0x65, 0xc5, 0x01, 0xc4, 0x0a, + 0x47, 0xf4, 0x3b, 0x1a, 0x9c, 0x4f, 0xdc, 0xe3, 0xa5, 0xe2, 0x59, 0x7c, 0x21, 0x26, 0x24, 0xc9, + 0x3a, 0x27, 0x79, 0x18, 0xe8, 0x4d, 0x4d, 0x9a, 0xb2, 0x9d, 0x20, 0x1e, 0x99, 0x65, 0x43, 0xbb, + 0x3d, 0xa1, 0x3f, 0x1a, 0x5a, 0xef, 0x80, 0x70, 0xf5, 0x5c, 0xc4, 0x32, 0x06, 0x8d, 0x38, 0xce, + 0x1e, 0x7d, 0x55, 0x0b, 0x4c, 0xa3, 0x1c, 0xd1, 0xdc, 0x59, 0x8d, 0x08, 0x85, 0x96, 0x56, 0x0e, + 0x28, 0xc6, 0x1c, 0x7d, 0x0c, 0x2e, 0x1a, 0xfb, 0x8e, 0xeb, 0x27, 0x6e, 0xbe, 0xd2, 0x3c, 0xdb, + 0x46, 0x2b, 0xbd, 0x6e, 0xf9, 0x62, 0x65, 0x20, 0x16, 0x1e, 0x42, 0x41, 0xff, 0xe7, 0x2c, 0xcc, + 0xae, 0x1b, 0xb6, 0xe1, 0x1e, 0x0b, 0xd3, 0xf5, 0x0d, 0x0d, 0x9e, 0xac, 0x77, 0x5c, 0x97, 0xd8, + 0x7e, 0xcd, 0x27, 0xed, 0x7e, 0xc3, 0xa5, 0x9d, 0xa9, 0xe1, 0xba, 0xdc, 0xeb, 0x96, 0x9f, 0x5c, + 0x1f, 0xc2, 0x1f, 0x0f, 0x1d, 0x1d, 0xfa, 0x6b, 0x0d, 0x74, 0x81, 0x50, 0x35, 0xea, 0xf7, 0x9b, + 0xae, 0xd3, 0xb1, 0x1b, 0xfd, 0x93, 0xc8, 0x9c, 0xe9, 0x24, 0x9e, 0xee, 0x75, 0xcb, 0xfa, 0xfa, + 0x89, 0xa3, 0xc0, 0x23, 0x8c, 0x14, 0xbd, 0x04, 0x4b, 0x02, 0xeb, 0xda, 0xc3, 0x36, 0x71, 0x4d, + 0xea, 0xfb, 0x8a, 0x73, 0xfe, 0xc7, 0x85, 0x59, 0x59, 0x5a, 0x8f, 0x23, 0xe0, 0xfe, 0x3e, 0xfa, + 0x1f, 0xe7, 0x00, 0x82, 0x95, 0x26, 0x6d, 0xf4, 0x33, 0x30, 0xe3, 0x11, 0xff, 0x2e, 0x31, 0x9b, + 0x87, 0xc1, 0xc9, 0x0d, 0x3f, 0x0e, 0x0a, 0x1a, 0x71, 0x08, 0x47, 0xf7, 0x21, 0xdf, 0x36, 0x3a, + 0x1e, 0x11, 0xdf, 0x6d, 0x2b, 0x95, 0xef, 0xb6, 0x4b, 0x29, 0xf2, 0xd8, 0x82, 0xfd, 0x89, 0x39, + 0x0f, 0xf4, 0x79, 0x0d, 0x80, 0xa8, 0x73, 0x2d, 0x5e, 0xad, 0xa5, 0xc2, 0x32, 0xfc, 0x1c, 0xf4, + 0x1b, 0x54, 0xe7, 0x7b, 0xdd, 0x32, 0x44, 0xbe, 0x5a, 0x84, 0x2d, 0x7a, 0x00, 0x05, 0x23, 0x50, + 0x97, 0xb9, 0xb3, 0x50, 0x97, 0xcc, 0xe5, 0x97, 0xeb, 0x2d, 0x99, 0xa1, 0x2f, 0x6b, 0x30, 0xef, + 0x11, 0x5f, 0x2c, 0x15, 0xdd, 0xb4, 0xc2, 0x57, 0xdc, 0x9e, 0x8c, 0x7f, 0x4d, 0xa1, 0xc9, 0x95, + 0x8f, 0xda, 0x86, 0x63, 0x7c, 0xf5, 0x37, 0x8b, 0x30, 0x1f, 0x88, 0x4c, 0xe8, 0xfe, 0xd5, 0x79, + 0x4b, 0xb2, 0xfb, 0xb7, 0x1e, 0x05, 0x62, 0x15, 0x97, 0x76, 0xf6, 0x7c, 0xea, 0x6f, 0xa8, 0xde, + 0x9f, 0xec, 0x5c, 0x8b, 0x02, 0xb1, 0x8a, 0x8b, 0x5a, 0x90, 0xf7, 0x7c, 0xd2, 0x0e, 0x0e, 0x5b, + 0x6f, 0x4c, 0xf6, 0x35, 0xc2, 0x9d, 0x10, 0x1e, 0x28, 0xd1, 0x5f, 0x1e, 0xe6, 0x5c, 0xd0, 0xd7, + 0x34, 0x98, 0xf7, 0x95, 0x9c, 0x96, 0x10, 0x83, 0x74, 0x24, 0x51, 0x4d, 0x97, 0xf1, 0xd5, 0x50, + 0xdb, 0x70, 0x8c, 0x7d, 0x82, 0x47, 0x98, 0x3f, 0x43, 0x8f, 0xf0, 0x55, 0x28, 0xb4, 0x8c, 0x87, + 0xb5, 0x8e, 0xdb, 0x3c, 0xbd, 0xe7, 0x29, 0x52, 0x7e, 0x9c, 0x0a, 0x96, 0xf4, 0xd0, 0xe7, 0xb4, + 0xc8, 0xe6, 0x9a, 0x66, 0xc4, 0xef, 0xa6, 0xbb, 0xb9, 0xa4, 0x42, 0x1d, 0xb8, 0xcd, 0xfa, 0xfc, + 0xb3, 0xc2, 0x23, 0xf7, 0xcf, 0xa8, 0xaf, 0xc1, 0x37, 0x88, 0xf4, 0x35, 0x66, 0xce, 0xd4, 0xd7, + 0x58, 0x57, 0x98, 0xe1, 0x18, 0x73, 0x36, 0x1e, 0xbe, 0xe7, 0xe4, 0x78, 0xe0, 0x4c, 0xc7, 0x53, + 0x53, 0x98, 0xe1, 0x18, 0xf3, 0xc1, 0x41, 0x49, 0xf1, 0x6c, 0x82, 0x92, 0xd9, 0x14, 0x82, 0x92, + 0xe1, 0xfe, 0xda, 0xdc, 0xc4, 0xfe, 0xda, 0x8f, 0x34, 0xb8, 0xb0, 0x6e, 0x75, 0x3c, 0x9f, 0xb8, + 0xff, 0x6b, 0xf2, 0x18, 0xff, 0xa1, 0xc1, 0x13, 0x03, 0xe6, 0xfc, 0x08, 0xd2, 0x19, 0x6f, 0xa8, + 0xe9, 0x8c, 0x3b, 0x13, 0xda, 0x9d, 0xe4, 0x79, 0x0c, 0xc8, 0x6a, 0xf8, 0x30, 0xb7, 0x61, 0xf8, + 0x46, 0xc3, 0x69, 0xf2, 0x34, 0x03, 0x7a, 0x11, 0x0a, 0xa6, 0xed, 0x13, 0xf7, 0xc8, 0xb0, 0x84, + 0xe5, 0xd5, 0x83, 0xa1, 0x6f, 0x8a, 0xf6, 0x77, 0xba, 0xe5, 0xf9, 0x8d, 0x8e, 0xcb, 0x0a, 0x35, + 0xb8, 0x1e, 0xc6, 0xb2, 0x0f, 0x7a, 0x0a, 0xf2, 0x9f, 0xe8, 0x10, 0xf7, 0x38, 0x9e, 0xd6, 0xbf, + 0x4d, 0x1b, 0x31, 0x87, 0xe9, 0x7f, 0x97, 0x81, 0x88, 0x57, 0xf4, 0x08, 0xc4, 0xca, 0x56, 0xc4, + 0x6a, 0x42, 0x3f, 0x27, 0xe2, 0xe3, 0x0d, 0xaa, 0xc7, 0x38, 0x8a, 0xd5, 0x63, 0xdc, 0x4c, 0x8d, + 0xe3, 0xf0, 0x72, 0x8c, 0xb7, 0x35, 0x78, 0x22, 0x44, 0xee, 0xf7, 0xf5, 0x4f, 0x3e, 0x98, 0x7f, + 0x0e, 0x8a, 0x46, 0xd8, 0x4d, 0xac, 0xa2, 0xac, 0xf7, 0x89, 0x50, 0xc4, 0x51, 0xbc, 0x30, 0x25, + 0x9e, 0x3d, 0x65, 0x4a, 0x3c, 0x37, 0x3c, 0x25, 0xae, 0xff, 0x38, 0x03, 0x97, 0xfa, 0x67, 0x16, + 0x48, 0x37, 0x26, 0x07, 0x23, 0xcc, 0xed, 0x79, 0x98, 0xf5, 0x45, 0x07, 0xda, 0x2a, 0x26, 0xb7, + 0x2c, 0x30, 0x67, 0xf7, 0x22, 0x30, 0xac, 0x60, 0xd2, 0x9e, 0x75, 0xbe, 0xaf, 0x6a, 0x75, 0xa7, + 0x1d, 0xd4, 0x0e, 0xc8, 0x9e, 0xeb, 0x11, 0x18, 0x56, 0x30, 0x65, 0xb2, 0x2e, 0x77, 0xe6, 0xc5, + 0x0d, 0x35, 0x38, 0x1f, 0xe4, 0x6c, 0xae, 0x3b, 0xee, 0xba, 0xd3, 0x6a, 0x5b, 0x84, 0xa5, 0x9c, + 0xf2, 0x6c, 0xb0, 0x97, 0x44, 0x97, 0xf3, 0x38, 0x09, 0x09, 0x27, 0xf7, 0xd5, 0xdf, 0xce, 0xc2, + 0xb9, 0xf0, 0xb3, 0xaf, 0x3b, 0x76, 0xc3, 0x64, 0x99, 0xaf, 0x17, 0x20, 0xe7, 0x1f, 0xb7, 0x83, + 0x8f, 0xfd, 0xff, 0x82, 0xe1, 0xec, 0x1d, 0xb7, 0xe9, 0x6a, 0x5f, 0x48, 0xe8, 0x42, 0x41, 0x98, + 0x75, 0x42, 0xdb, 0x72, 0x77, 0xf0, 0x15, 0x78, 0x56, 0x95, 0xe6, 0x77, 0xba, 0xe5, 0x84, 0x2a, + 0xbf, 0x55, 0x49, 0x49, 0x95, 0x79, 0x74, 0x0f, 0xe6, 0x2d, 0xc3, 0xf3, 0xef, 0xb4, 0x1b, 0x86, + 0x4f, 0xf6, 0xcc, 0x16, 0x11, 0x7b, 0x6e, 0x9c, 0x3a, 0x05, 0x79, 0x3c, 0xbc, 0xad, 0x50, 0xc2, + 0x31, 0xca, 0xe8, 0x08, 0x10, 0x6d, 0xd9, 0x73, 0x0d, 0xdb, 0xe3, 0xb3, 0xa2, 0xfc, 0xc6, 0xaf, + 0x8b, 0xb8, 0x28, 0xf8, 0xa1, 0xed, 0x3e, 0x6a, 0x38, 0x81, 0x03, 0x7a, 0x1a, 0xa6, 0x5c, 0x62, + 0x78, 0x62, 0x31, 0x67, 0xc2, 0xfd, 0x8f, 0x59, 0x2b, 0x16, 0xd0, 0xe8, 0x86, 0x9a, 0x3a, 0x61, + 0x43, 0x7d, 0x4f, 0x83, 0xf9, 0x70, 0x99, 0x1e, 0x81, 0x99, 0x6b, 0xa9, 0x66, 0xee, 0x46, 0x5a, + 0x2a, 0x71, 0x80, 0x65, 0xfb, 0x93, 0x5c, 0x74, 0x7e, 0x2c, 0x53, 0xff, 0x49, 0x98, 0x09, 0x76, + 0x75, 0x90, 0xab, 0x9f, 0xd0, 0x1b, 0x57, 0x3c, 0x8b, 0x48, 0x29, 0x91, 0x60, 0x82, 0x43, 0x7e, + 0xd4, 0xb0, 0x36, 0x84, 0xd1, 0x14, 0x62, 0x2f, 0x0d, 0x6b, 0x60, 0x4c, 0x93, 0x0c, 0x6b, 0xd0, + 0x07, 0xdd, 0x81, 0x0b, 0x6d, 0xd7, 0x61, 0xb5, 0x9c, 0x1b, 0xc4, 0x68, 0x58, 0xa6, 0x4d, 0x02, + 0xa7, 0x8f, 0x67, 0x27, 0x9e, 0xe8, 0x75, 0xcb, 0x17, 0x76, 0x93, 0x51, 0xf0, 0xa0, 0xbe, 0x6a, + 0x49, 0x54, 0xee, 0xe4, 0x92, 0x28, 0xf4, 0xcb, 0x32, 0xb4, 0x22, 0x5e, 0x29, 0xcf, 0x3e, 0xe2, + 0x6b, 0x69, 0x2d, 0x65, 0x82, 0x5a, 0x0f, 0x45, 0xaa, 0x22, 0x98, 0x62, 0xc9, 0x7e, 0xb0, 0xff, + 0x3e, 0x75, 0x3a, 0xff, 0x5d, 0xff, 0x42, 0x1e, 0x16, 0xe3, 0xc6, 0xf6, 0xec, 0xcb, 0xbd, 0x7e, + 0x4d, 0x83, 0xc5, 0x40, 0x50, 0x38, 0x4f, 0x12, 0x1c, 0x42, 0x6c, 0xa7, 0x24, 0x9f, 0xdc, 0x6d, + 0x90, 0xb5, 0xb7, 0x7b, 0x31, 0x6e, 0xb8, 0x8f, 0x3f, 0x7a, 0x1d, 0x8a, 0x32, 0x56, 0x3f, 0x55, + 0xed, 0xd7, 0x02, 0x73, 0x18, 0x42, 0x12, 0x38, 0x4a, 0x0f, 0x7d, 0x41, 0x03, 0xa8, 0x07, 0x1a, + 0x3d, 0x10, 0xa4, 0xdb, 0x69, 0x09, 0x92, 0xb4, 0x15, 0xa1, 0x5f, 0x28, 0x9b, 0x3c, 0x1c, 0x61, + 0x8c, 0x7e, 0x9d, 0x45, 0xe9, 0xd2, 0x91, 0xa1, 0xa2, 0x43, 0x47, 0xf2, 0x91, 0xb4, 0x45, 0x3a, + 0x3c, 0xbb, 0x95, 0x5e, 0x43, 0x04, 0xe4, 0x61, 0x65, 0x10, 0xfa, 0x0b, 0x20, 0x73, 0xf5, 0x74, + 0x87, 0xb2, 0x6c, 0xfd, 0xae, 0xe1, 0x1f, 0x0a, 0x11, 0x94, 0x3b, 0xf4, 0x7a, 0x00, 0xc0, 0x21, + 0x8e, 0xfe, 0x17, 0x1a, 0x2c, 0x6f, 0x7a, 0xbe, 0xe9, 0x6c, 0x10, 0xcf, 0xa7, 0x9b, 0x96, 0xda, + 0xf7, 0x8e, 0x45, 0x46, 0xf0, 0x90, 0x36, 0x60, 0x51, 0x1c, 0xa8, 0x75, 0xf6, 0x3d, 0xe2, 0x47, + 0xbc, 0x24, 0x29, 0x3a, 0xeb, 0x31, 0x38, 0xee, 0xeb, 0x41, 0xa9, 0x88, 0x93, 0xb5, 0x90, 0x4a, + 0x56, 0xa5, 0x52, 0x8b, 0xc1, 0x71, 0x5f, 0x0f, 0xfd, 0x9b, 0x19, 0x38, 0xc7, 0xa6, 0x11, 0x2b, + 0xfc, 0xfe, 0x55, 0x0d, 0xe6, 0x8f, 0x4c, 0xd7, 0xef, 0x18, 0x56, 0xf4, 0x88, 0x70, 0x62, 0xe9, + 0x61, 0xbc, 0x5e, 0x51, 0x08, 0x87, 0x7e, 0x81, 0xda, 0x8e, 0x63, 0x03, 0xa0, 0x63, 0x5a, 0x68, + 0xa8, 0x5f, 0x3b, 0x9d, 0x10, 0x36, 0x69, 0x1d, 0x79, 0xa2, 0x29, 0xd6, 0x88, 0xe3, 0xfc, 0xf5, + 0xd7, 0xc4, 0xe7, 0x53, 0x87, 0x3e, 0x82, 0x10, 0xe8, 0x30, 0xe5, 0x3a, 0x1d, 0x6a, 0x23, 0x33, + 0xac, 0xae, 0x1e, 0x98, 0xa3, 0xc1, 0x5a, 0xb0, 0x80, 0xe8, 0x7f, 0xa4, 0xc1, 0xcc, 0x96, 0xb3, + 0x2f, 0x82, 0xc6, 0x8f, 0xa5, 0x10, 0xc0, 0x49, 0x3d, 0x2f, 0x4f, 0x6b, 0x42, 0xd7, 0xe1, 0x45, + 0x25, 0x7c, 0x7b, 0x32, 0x42, 0x7b, 0x95, 0x5d, 0x14, 0xa1, 0xa4, 0xb6, 0x9c, 0xfd, 0x81, 0xf1, + 0xfd, 0xef, 0xe5, 0x61, 0xee, 0x65, 0xe3, 0x98, 0xd8, 0xbe, 0x21, 0x46, 0xfc, 0x5e, 0x98, 0x36, + 0x1a, 0x8d, 0xa4, 0x8b, 0x13, 0x15, 0xde, 0x8c, 0x03, 0x38, 0x8b, 0x88, 0xda, 0x2c, 0xaf, 0x1f, + 0xb1, 0xdd, 0x61, 0x44, 0x14, 0x82, 0x70, 0x14, 0x2f, 0xdc, 0x4a, 0xeb, 0x8e, 0x7d, 0x60, 0x36, + 0x93, 0x36, 0xc1, 0x7a, 0x0c, 0x8e, 0xfb, 0x7a, 0xa0, 0x2d, 0x40, 0xa2, 0xec, 0xaf, 0x52, 0xaf, + 0x3b, 0x1d, 0x9b, 0x6f, 0x26, 0x1e, 0x2c, 0x49, 0x27, 0x72, 0xa7, 0x0f, 0x03, 0x27, 0xf4, 0x42, + 0x1f, 0x85, 0x52, 0x9d, 0x51, 0x16, 0x2e, 0x45, 0x94, 0x22, 0x77, 0x2b, 0x65, 0x4d, 0xcd, 0xfa, + 0x00, 0x3c, 0x3c, 0x90, 0x02, 0x1d, 0xa9, 0xe7, 0x3b, 0xae, 0xd1, 0x24, 0x51, 0xba, 0x53, 0xea, + 0x48, 0x6b, 0x7d, 0x18, 0x38, 0xa1, 0x17, 0xfa, 0x0c, 0xcc, 0xf8, 0x87, 0x2e, 0xf1, 0x0e, 0x1d, + 0xab, 0x21, 0x8e, 0x6f, 0x27, 0x8c, 0xa0, 0xc5, 0xea, 0xef, 0x05, 0x54, 0x23, 0x4e, 0x4e, 0xd0, + 0x84, 0x43, 0x9e, 0xc8, 0x85, 0x29, 0x8f, 0x86, 0x6f, 0x5e, 0xa9, 0x90, 0x86, 0x9b, 0x28, 0xb8, + 0xb3, 0x88, 0x30, 0x12, 0xbb, 0x33, 0x0e, 0x58, 0x70, 0xd2, 0xbf, 0x9d, 0x81, 0xd9, 0x28, 0xe2, + 0x08, 0x3b, 0xf5, 0xf3, 0x1a, 0xcc, 0xd6, 0x1d, 0xdb, 0x77, 0x1d, 0x8b, 0xc7, 0xa5, 0x7c, 0x83, + 0x4c, 0x78, 0x6d, 0x80, 0x91, 0xda, 0x20, 0xbe, 0x61, 0x5a, 0x91, 0x10, 0x37, 0xc2, 0x06, 0x2b, + 0x4c, 0xd1, 0x57, 0x34, 0x58, 0x08, 0xf3, 0x5a, 0x61, 0x80, 0x9c, 0xea, 0x40, 0x64, 0xe9, 0xd9, + 0x35, 0x95, 0x13, 0x8e, 0xb3, 0xd6, 0xf7, 0x61, 0x31, 0xbe, 0xda, 0xf4, 0x53, 0xb6, 0x0d, 0xb1, + 0xd7, 0xb3, 0xe1, 0xa7, 0xdc, 0x35, 0x3c, 0x0f, 0x33, 0x08, 0x7a, 0x1f, 0x14, 0x5a, 0x86, 0xdb, + 0x34, 0x6d, 0xc3, 0x62, 0x5f, 0x31, 0x1b, 0x51, 0x48, 0xa2, 0x1d, 0x4b, 0x0c, 0xfd, 0x87, 0x39, + 0x28, 0xee, 0x10, 0xc3, 0xeb, 0xb8, 0x84, 0x9d, 0x60, 0x9d, 0xb9, 0x8b, 0xa8, 0xd4, 0xe1, 0x67, + 0xd3, 0xab, 0xc3, 0x47, 0xaf, 0x02, 0x1c, 0x98, 0xb6, 0xe9, 0x1d, 0x9e, 0xb2, 0xc2, 0x9f, 0x65, + 0x38, 0xaf, 0x4b, 0x0a, 0x38, 0x42, 0x2d, 0xbc, 0xe2, 0x93, 0x1f, 0x72, 0xc5, 0xe7, 0x0b, 0x5a, + 0xc4, 0x78, 0x70, 0xe7, 0xeb, 0xee, 0xa4, 0x05, 0xd4, 0x72, 0x61, 0x56, 0x03, 0x63, 0x72, 0xcd, + 0xf6, 0xdd, 0xe3, 0xa1, 0x36, 0x66, 0x0f, 0x0a, 0x2e, 0xf1, 0x3a, 0x2d, 0xea, 0xec, 0x4e, 0x8f, + 0xfd, 0x19, 0x58, 0x12, 0x08, 0x8b, 0xfe, 0x58, 0x52, 0xba, 0xf8, 0x02, 0xcc, 0x29, 0x43, 0x40, + 0x8b, 0x90, 0xbd, 0x4f, 0x8e, 0xb9, 0x9c, 0x60, 0xfa, 0x27, 0x5a, 0x56, 0x4a, 0x61, 0xc5, 0x67, + 0xf9, 0x50, 0xe6, 0x79, 0x4d, 0xff, 0xf1, 0x14, 0x4c, 0x09, 0x7b, 0x75, 0xb2, 0x2e, 0x88, 0x1e, + 0xdc, 0x66, 0x4e, 0x71, 0x70, 0xbb, 0x05, 0xb3, 0xa6, 0x6d, 0xfa, 0xa6, 0x61, 0xb1, 0x88, 0x48, + 0xd8, 0xaa, 0xa7, 0x83, 0xfd, 0xbf, 0x19, 0x81, 0x25, 0xd0, 0x51, 0xfa, 0xa2, 0xdb, 0x90, 0x67, + 0xca, 0x5c, 0xc8, 0xd3, 0xf8, 0x79, 0x3d, 0x96, 0xb3, 0xe7, 0xb5, 0x75, 0x9c, 0x12, 0xf3, 0x29, + 0xf9, 0xa5, 0x0b, 0xe9, 0xc8, 0x0b, 0xb1, 0x0a, 0x7d, 0xca, 0x18, 0x1c, 0xf7, 0xf5, 0xa0, 0x54, + 0x0e, 0x0c, 0xd3, 0xea, 0xb8, 0x24, 0xa4, 0x32, 0xa5, 0x52, 0xb9, 0x1e, 0x83, 0xe3, 0xbe, 0x1e, + 0xe8, 0x00, 0x66, 0x45, 0x1b, 0x4f, 0xeb, 0x4c, 0x9f, 0x72, 0x96, 0x2c, 0x7d, 0x77, 0x3d, 0x42, + 0x09, 0x2b, 0x74, 0x51, 0x07, 0x96, 0x4c, 0xbb, 0xee, 0xd8, 0x75, 0xab, 0xe3, 0x99, 0x47, 0x24, + 0x2c, 0x6c, 0x3b, 0x0d, 0xb3, 0xf3, 0xbd, 0x6e, 0x79, 0x69, 0x33, 0x4e, 0x0e, 0xf7, 0x73, 0x40, + 0x9f, 0xd3, 0xe0, 0x7c, 0xdd, 0xb1, 0x3d, 0x56, 0xda, 0x7d, 0x44, 0xae, 0xb9, 0xae, 0xe3, 0x72, + 0xde, 0x33, 0xa7, 0xe4, 0xcd, 0x02, 0xf1, 0xf5, 0x24, 0x92, 0x38, 0x99, 0x13, 0x7a, 0x03, 0x0a, + 0x6d, 0xd7, 0x39, 0x32, 0x1b, 0xc4, 0x15, 0x29, 0xc2, 0xed, 0x34, 0x6e, 0x55, 0xec, 0x0a, 0x9a, + 0xa1, 0x26, 0x08, 0x5a, 0xb0, 0xe4, 0xa7, 0x7f, 0x7d, 0x0a, 0xe6, 0x55, 0x74, 0xf4, 0x69, 0x80, + 0xb6, 0xeb, 0xb4, 0x88, 0x7f, 0x48, 0x64, 0x81, 0xd2, 0xcd, 0x49, 0x6f, 0x34, 0x04, 0xf4, 0xc4, + 0x85, 0x0f, 0xa6, 0x49, 0xc3, 0x56, 0x1c, 0xe1, 0x88, 0x5c, 0x98, 0xbe, 0xcf, 0x6d, 0x9a, 0x30, + 0xf1, 0x2f, 0xa7, 0xe2, 0x90, 0x08, 0xce, 0x45, 0x6a, 0x72, 0x44, 0x13, 0x0e, 0x18, 0xa1, 0x7d, + 0xc8, 0x3e, 0x20, 0xfb, 0xe9, 0xd4, 0xde, 0xdf, 0x25, 0x22, 0x54, 0xa8, 0x4e, 0xf7, 0xba, 0xe5, + 0xec, 0x5d, 0xb2, 0x8f, 0x29, 0x71, 0x3a, 0xaf, 0x06, 0x4f, 0x3f, 0x09, 0x55, 0x31, 0xe1, 0xbc, + 0x94, 0x5c, 0x16, 0x9f, 0x97, 0x68, 0xc2, 0x01, 0x23, 0xf4, 0x06, 0xcc, 0x3c, 0x30, 0x8e, 0xc8, + 0x81, 0xeb, 0xd8, 0xbe, 0x28, 0x70, 0x98, 0xb0, 0xf0, 0xe6, 0x6e, 0x40, 0x4e, 0xf0, 0x65, 0xd6, + 0x56, 0x36, 0xe2, 0x90, 0x1d, 0x3a, 0x82, 0x82, 0x4d, 0x1e, 0x60, 0x62, 0x99, 0x75, 0x51, 0xf3, + 0x30, 0xa1, 0x58, 0xdf, 0x14, 0xd4, 0x04, 0x67, 0x66, 0x86, 0x82, 0x36, 0x2c, 0x79, 0xd1, 0xb5, + 0xbc, 0xe7, 0xec, 0x0b, 0x45, 0x35, 0xe1, 0x5a, 0xca, 0xb0, 0x8f, 0xaf, 0xe5, 0x96, 0xb3, 0x8f, + 0x29, 0x71, 0xfd, 0x9b, 0x39, 0x98, 0x8d, 0xde, 0x25, 0x1c, 0xc1, 0x66, 0x49, 0xb7, 0x29, 0x33, + 0x8e, 0xdb, 0x44, 0xbd, 0xde, 0x56, 0x68, 0xe3, 0x83, 0xa3, 0xb2, 0xcd, 0xd4, 0xbc, 0x86, 0xd0, + 0xeb, 0x8d, 0x34, 0x7a, 0x58, 0x61, 0x3a, 0x46, 0xee, 0x8a, 0xfa, 0x41, 0xdc, 0x1c, 0xf2, 0x62, + 0x6d, 0xe9, 0x07, 0x29, 0x06, 0xee, 0x2a, 0x40, 0x78, 0xab, 0x50, 0x1c, 0x60, 0xca, 0xc3, 0xab, + 0xc8, 0x6d, 0xc7, 0x08, 0x16, 0x7a, 0x1a, 0xa6, 0xa8, 0xc1, 0x20, 0x0d, 0x51, 0x45, 0x2d, 0x43, + 0x8b, 0xeb, 0xac, 0x15, 0x0b, 0x28, 0x7a, 0x9e, 0xda, 0xf6, 0x50, 0xcd, 0x8b, 0xe2, 0xe8, 0xe5, + 0xd0, 0xb6, 0x87, 0x30, 0xac, 0x60, 0xd2, 0xa1, 0x13, 0xaa, 0x95, 0x99, 0xea, 0x8f, 0x0c, 0x9d, + 0xa9, 0x6a, 0xcc, 0x61, 0x2c, 0xd4, 0x8d, 0x69, 0x71, 0xa6, 0xb4, 0xf3, 0x91, 0x50, 0x37, 0x06, + 0xc7, 0x7d, 0x3d, 0xf4, 0x8f, 0xc3, 0xbc, 0x2a, 0xcd, 0xf4, 0x13, 0xb7, 0x5d, 0xe7, 0xc0, 0xb4, + 0x48, 0x3c, 0x48, 0xdf, 0xe5, 0xcd, 0x38, 0x80, 0x8f, 0x96, 0x76, 0xfe, 0xcb, 0x2c, 0x9c, 0xbb, + 0xd9, 0x34, 0xed, 0x87, 0xb1, 0x13, 0xa5, 0xa4, 0xc7, 0x0a, 0xb4, 0x71, 0x1f, 0x2b, 0x08, 0x6b, + 0xcf, 0xc4, 0xd3, 0x0b, 0xc9, 0xb5, 0x67, 0xc1, 0xbb, 0x0c, 0x2a, 0x2e, 0xfa, 0x9e, 0x06, 0x4f, + 0x1a, 0x0d, 0xee, 0x5f, 0x18, 0x96, 0x68, 0x0d, 0x99, 0x06, 0x32, 0xee, 0x4d, 0xa8, 0x2d, 0xfa, + 0x27, 0xbf, 0x5a, 0x19, 0xc2, 0x95, 0x7b, 0xcd, 0xef, 0x11, 0x33, 0x78, 0x72, 0x18, 0x2a, 0x1e, + 0x3a, 0xfc, 0x8b, 0xb7, 0xe0, 0xdd, 0x27, 0x32, 0x1a, 0xcb, 0x37, 0xfe, 0xbc, 0x06, 0x33, 0xfc, + 0xf4, 0x08, 0x93, 0x03, 0xba, 0x79, 0x8c, 0xb6, 0xf9, 0x0a, 0x71, 0xbd, 0xe0, 0xc6, 0xe1, 0x4c, + 0xb8, 0x79, 0x2a, 0xbb, 0x9b, 0x02, 0x82, 0x23, 0x58, 0x54, 0x3d, 0xdd, 0x37, 0xed, 0x86, 0x58, + 0x26, 0xa9, 0x9e, 0x5e, 0x36, 0xed, 0x06, 0x66, 0x10, 0xa9, 0xc0, 0xb2, 0x83, 0x14, 0x98, 0xfe, + 0xfb, 0x1a, 0xcc, 0xb3, 0xd2, 0xd2, 0xd0, 0x39, 0x7c, 0x4e, 0xa6, 0xea, 0xf8, 0x30, 0x2e, 0xa9, + 0xa9, 0xba, 0x77, 0xba, 0xe5, 0x22, 0x2f, 0x46, 0x55, 0x33, 0x77, 0xaf, 0x89, 0x00, 0x8f, 0x25, + 0x14, 0x33, 0x63, 0xc7, 0x1f, 0xf2, 0x38, 0xa3, 0x16, 0x10, 0xc1, 0x21, 0x3d, 0xfd, 0xeb, 0x59, + 0x38, 0x97, 0x50, 0x23, 0x45, 0x63, 0xaf, 0x29, 0xcb, 0xd8, 0x27, 0x56, 0x90, 0x0e, 0x7b, 0x3d, + 0xf5, 0x3a, 0xac, 0xd5, 0x6d, 0x46, 0x9f, 0x4b, 0x92, 0xd4, 0x4f, 0xbc, 0x11, 0x0b, 0xe6, 0xe8, + 0x37, 0x35, 0x28, 0x1a, 0x11, 0x61, 0xe7, 0x19, 0xc2, 0xfd, 0xf4, 0x07, 0xd3, 0x27, 0xdb, 0x91, + 0xca, 0x86, 0x50, 0x94, 0xa3, 0x63, 0xb9, 0xf8, 0xf3, 0x50, 0x8c, 0x4c, 0x61, 0x1c, 0x19, 0xbd, + 0xf8, 0x22, 0x2c, 0x4e, 0x24, 0xe3, 0x1f, 0x81, 0x71, 0xaf, 0xb0, 0x52, 0x8b, 0xf0, 0x20, 0x5a, + 0x71, 0x2d, 0xbf, 0xb8, 0x28, 0xb9, 0x16, 0x50, 0x7d, 0x1f, 0x16, 0xe3, 0x0e, 0xe8, 0x38, 0x67, + 0xa2, 0x23, 0xa9, 0xdb, 0x0f, 0xc0, 0x98, 0x97, 0x4e, 0xf5, 0xbf, 0xca, 0xc0, 0xb4, 0x28, 0xb4, + 0x7c, 0x04, 0x45, 0x41, 0xf7, 0x95, 0x53, 0xe5, 0xcd, 0x54, 0xea, 0x43, 0x07, 0x56, 0x04, 0x79, + 0xb1, 0x8a, 0xa0, 0x97, 0xd3, 0x61, 0x37, 0xbc, 0x1c, 0xe8, 0x6b, 0x19, 0x58, 0x88, 0x15, 0xae, + 0xa2, 0x2f, 0x6a, 0xfd, 0x59, 0xf0, 0x3b, 0xa9, 0xd6, 0xc6, 0xca, 0x92, 0xb3, 0xe1, 0x09, 0x71, + 0x4f, 0xb9, 0xc6, 0x7e, 0x3b, 0xb5, 0xa7, 0x4e, 0x86, 0xde, 0x68, 0xff, 0x27, 0x0d, 0x1e, 0x1f, + 0x58, 0xca, 0xcb, 0xae, 0x0b, 0xb9, 0x2a, 0x54, 0xc8, 0x5e, 0xca, 0xa5, 0xf9, 0xf2, 0x34, 0x33, + 0x7e, 0xc3, 0x23, 0xce, 0x1e, 0x3d, 0x0b, 0xb3, 0x4c, 0x8f, 0xd3, 0xed, 0xe3, 0x93, 0xb6, 0x78, + 0xa3, 0x82, 0x9d, 0x1c, 0xd4, 0x22, 0xed, 0x58, 0xc1, 0xd2, 0x7f, 0x57, 0x83, 0xd2, 0xa0, 0xcb, + 0x23, 0x23, 0xf8, 0xe5, 0x3f, 0x17, 0x2b, 0xd0, 0x29, 0xf7, 0x15, 0xe8, 0xc4, 0x3c, 0xf3, 0xa0, + 0x16, 0x27, 0xe2, 0x14, 0x67, 0x4f, 0xa8, 0x3f, 0xf9, 0xaa, 0x06, 0x17, 0x06, 0x08, 0x4e, 0x5f, + 0xa1, 0x96, 0x76, 0xea, 0x42, 0xad, 0xcc, 0xa8, 0x85, 0x5a, 0xfa, 0xdf, 0x64, 0x61, 0x51, 0x8c, + 0x27, 0x34, 0xe6, 0xcf, 0x2b, 0x65, 0x4e, 0xef, 0x89, 0x95, 0x39, 0x2d, 0xc7, 0xf1, 0xff, 0xaf, + 0xc6, 0xe9, 0xa7, 0xab, 0xc6, 0xe9, 0x27, 0x19, 0x38, 0x9f, 0x78, 0x31, 0x07, 0x7d, 0x39, 0x41, + 0x0b, 0xde, 0x4d, 0xf9, 0x06, 0xd0, 0x88, 0x7a, 0x70, 0xd2, 0xc2, 0xa0, 0xdf, 0x88, 0x16, 0xe4, + 0xf0, 0x30, 0xe1, 0xe0, 0x0c, 0xee, 0x32, 0x8d, 0x59, 0x9b, 0xa3, 0xff, 0x4a, 0x16, 0xae, 0x8c, + 0x4a, 0xe8, 0xa7, 0xb4, 0x76, 0xd3, 0x53, 0x6a, 0x37, 0x1f, 0x8d, 0x85, 0x3a, 0x9b, 0x32, 0xce, + 0x2f, 0x65, 0xa5, 0xd9, 0xeb, 0x97, 0xcf, 0x91, 0x92, 0x0b, 0xd3, 0xd4, 0x8b, 0x09, 0x9e, 0xa5, + 0x08, 0x55, 0xe1, 0x74, 0x8d, 0x37, 0xbf, 0xd3, 0x2d, 0x2f, 0x89, 0xdb, 0xef, 0x35, 0xe2, 0x8b, + 0x46, 0x1c, 0x74, 0x42, 0x57, 0xa0, 0xe0, 0x72, 0x68, 0x50, 0xad, 0x26, 0x12, 0x26, 0xbc, 0x0d, + 0x4b, 0x28, 0xfa, 0x4c, 0xc4, 0xed, 0xcb, 0x9d, 0xd5, 0xdd, 0x90, 0x61, 0x79, 0xa0, 0xd7, 0xa1, + 0xe0, 0x05, 0x6f, 0x56, 0xf0, 0xd3, 0xc1, 0x67, 0x46, 0x2c, 0x82, 0xa4, 0x51, 0x42, 0xf0, 0x80, + 0x05, 0x9f, 0x9f, 0x7c, 0xde, 0x42, 0x92, 0xd4, 0xdf, 0xd6, 0xa0, 0x28, 0x56, 0xe2, 0x11, 0xd4, + 0x5c, 0xde, 0x53, 0x6b, 0x2e, 0xaf, 0xa5, 0xa2, 0x17, 0x06, 0x14, 0x5c, 0xde, 0x83, 0xd9, 0xe8, + 0xbd, 0x4b, 0xf4, 0x6a, 0x44, 0xaf, 0x69, 0x93, 0xdc, 0xef, 0x0a, 0x34, 0x5f, 0xa8, 0xf3, 0xf4, + 0xb7, 0xa6, 0xe5, 0x57, 0x64, 0x95, 0x9d, 0x51, 0xf9, 0xd2, 0x86, 0xca, 0x57, 0x74, 0x79, 0x33, + 0xa9, 0x2f, 0x2f, 0xba, 0x0d, 0x85, 0x40, 0xf9, 0x08, 0x13, 0xfd, 0x54, 0xb4, 0x5a, 0x85, 0xda, + 0x79, 0x4a, 0x2c, 0x22, 0x94, 0x2c, 0x62, 0x90, 0x6b, 0x28, 0x95, 0xa2, 0x24, 0x83, 0xde, 0x80, + 0xe2, 0x03, 0xc7, 0xbd, 0x6f, 0x39, 0x06, 0x7b, 0x16, 0x06, 0xd2, 0x38, 0xc3, 0x95, 0x27, 0x27, + 0xbc, 0x4a, 0xef, 0x6e, 0x48, 0x1f, 0x47, 0x99, 0xa1, 0x0a, 0x2c, 0xb4, 0x4c, 0x1b, 0x13, 0xa3, + 0x21, 0x4b, 0x2b, 0x73, 0xfc, 0x29, 0x8c, 0xc0, 0x81, 0xdd, 0x51, 0xc1, 0x38, 0x8e, 0x8f, 0x3e, + 0x09, 0x05, 0x4f, 0xdc, 0xed, 0x4c, 0xe7, 0xb4, 0x5d, 0x86, 0x3e, 0x9c, 0x68, 0xf8, 0xed, 0x82, + 0x16, 0x2c, 0x19, 0xa2, 0x6d, 0x58, 0x76, 0xc5, 0xed, 0x29, 0xe5, 0xa5, 0x37, 0x7e, 0xbc, 0xca, + 0x5e, 0x5c, 0xc0, 0x09, 0x70, 0x9c, 0xd8, 0x8b, 0x7a, 0x28, 0xec, 0x02, 0x31, 0x3f, 0x6e, 0x2d, + 0x84, 0x1e, 0x0a, 0x13, 0xf8, 0x06, 0x16, 0xd0, 0x61, 0xa5, 0xba, 0x85, 0x09, 0x4a, 0x75, 0xef, + 0xc2, 0x8c, 0x4b, 0x98, 0x9b, 0x5f, 0x09, 0x52, 0x71, 0x63, 0xd7, 0x00, 0xe0, 0x80, 0x00, 0x0e, + 0x69, 0xd1, 0x25, 0x32, 0xd4, 0x87, 0x1b, 0x6e, 0xa7, 0xf8, 0x7e, 0xa8, 0x58, 0xa6, 0x01, 0xd7, + 0x24, 0xf5, 0xff, 0x9c, 0x83, 0x39, 0x25, 0x9a, 0x45, 0x4f, 0x41, 0x9e, 0xdd, 0x4f, 0x63, 0x3b, + 0xb9, 0x10, 0x6a, 0x1b, 0x76, 0xa1, 0x0d, 0x73, 0x18, 0xfa, 0x9a, 0x06, 0x0b, 0x6d, 0xe5, 0xe4, + 0x2d, 0x50, 0x72, 0x13, 0x66, 0x54, 0xd4, 0xe3, 0xbc, 0xc8, 0x93, 0x47, 0x2a, 0x33, 0x1c, 0xe7, + 0x4e, 0xf7, 0x8a, 0x28, 0x8b, 0xb1, 0x88, 0xcb, 0xb0, 0x85, 0xab, 0x21, 0x49, 0xac, 0xab, 0x60, + 0x1c, 0xc7, 0xa7, 0x2b, 0xcc, 0x66, 0x37, 0xc9, 0x6b, 0x8b, 0x95, 0x80, 0x00, 0x0e, 0x69, 0xa1, + 0x17, 0x61, 0x5e, 0xdc, 0xd7, 0xdf, 0x75, 0x1a, 0x37, 0x0c, 0xef, 0x50, 0xf8, 0xd8, 0x32, 0x26, + 0x58, 0x57, 0xa0, 0x38, 0x86, 0xcd, 0xe6, 0x16, 0x3e, 0x8a, 0xc0, 0x08, 0x4c, 0xa9, 0x2f, 0x42, + 0xad, 0xab, 0x60, 0x1c, 0xc7, 0x47, 0xef, 0x8b, 0xa8, 0x68, 0x9e, 0xad, 0x90, 0x1b, 0x37, 0x41, + 0x4d, 0x57, 0x60, 0xa1, 0xc3, 0x42, 0x92, 0x46, 0x00, 0x14, 0x5b, 0x47, 0x32, 0xbc, 0xa3, 0x82, + 0x71, 0x1c, 0x1f, 0xbd, 0x00, 0x73, 0x2e, 0x55, 0x44, 0x92, 0x00, 0x4f, 0x61, 0xc8, 0xf3, 0x78, + 0x1c, 0x05, 0x62, 0x15, 0x17, 0xbd, 0x04, 0x4b, 0xe1, 0xcd, 0xe5, 0x80, 0x00, 0xcf, 0x69, 0xc8, + 0x47, 0x11, 0x2a, 0x71, 0x04, 0xdc, 0xdf, 0x07, 0xfd, 0x02, 0x2c, 0x46, 0xbe, 0xc4, 0xa6, 0xdd, + 0x20, 0x0f, 0xc5, 0xed, 0xd2, 0x65, 0x96, 0x17, 0x89, 0xc1, 0x70, 0x1f, 0x36, 0xfa, 0x10, 0xcc, + 0xd7, 0x1d, 0xcb, 0x62, 0xea, 0x88, 0xbf, 0x46, 0xc4, 0xaf, 0x91, 0xf2, 0x0b, 0xb7, 0x0a, 0x04, + 0xc7, 0x30, 0xd1, 0x16, 0x20, 0x67, 0xdf, 0x23, 0xee, 0x11, 0x69, 0xbc, 0xc4, 0x1f, 0x94, 0xa6, + 0xd6, 0x78, 0x4e, 0x2d, 0xca, 0xbb, 0xd5, 0x87, 0x81, 0x13, 0x7a, 0xa1, 0x7d, 0xb8, 0x18, 0x98, + 0x86, 0xfe, 0x1e, 0xa5, 0x92, 0x12, 0xb9, 0x5c, 0xbc, 0x3b, 0x10, 0x13, 0x0f, 0xa1, 0x82, 0x7e, + 0x49, 0xad, 0x0a, 0x9f, 0x4f, 0xe3, 0x7d, 0xc7, 0x78, 0x90, 0x7e, 0x62, 0x49, 0xb8, 0x0b, 0x53, + 0xbc, 0x0e, 0xb3, 0xb4, 0x90, 0xc6, 0x8d, 0xed, 0xe8, 0xe3, 0x27, 0xa1, 0xc9, 0xe0, 0xad, 0x58, + 0x70, 0x42, 0x9f, 0x86, 0x99, 0xfd, 0xe0, 0x25, 0xac, 0xd2, 0x62, 0x1a, 0x66, 0x32, 0xf6, 0xa8, + 0x5b, 0x18, 0x84, 0x4a, 0x00, 0x0e, 0x59, 0xa2, 0xa7, 0xa1, 0x78, 0x63, 0xb7, 0x22, 0x25, 0x7d, + 0x89, 0x49, 0x58, 0x8e, 0x76, 0xc1, 0x51, 0x00, 0xdd, 0xc5, 0xd2, 0x7d, 0x42, 0x6c, 0xc9, 0x43, + 0xf3, 0xdb, 0xef, 0x0d, 0x51, 0x6c, 0x96, 0xe6, 0xc2, 0xb5, 0xd2, 0xb9, 0x18, 0xb6, 0x68, 0xc7, + 0x12, 0x03, 0xbd, 0x0e, 0x45, 0x61, 0x93, 0x98, 0xfe, 0x5b, 0x3e, 0xdd, 0x8d, 0x03, 0x1c, 0x92, + 0xc0, 0x51, 0x7a, 0xe8, 0x39, 0x28, 0xb6, 0xd9, 0x03, 0x41, 0xe4, 0x7a, 0xc7, 0xb2, 0x4a, 0xe7, + 0x99, 0x6e, 0x96, 0xe7, 0xff, 0xbb, 0x21, 0x08, 0x47, 0xf1, 0xd0, 0x33, 0x41, 0x8e, 0xfa, 0x5d, + 0x4a, 0x3a, 0x47, 0xe6, 0xa8, 0xa5, 0xd3, 0x3b, 0xa0, 0xb2, 0xef, 0xc2, 0x09, 0x67, 0x14, 0x9f, + 0x0b, 0xcf, 0x68, 0xe5, 0x1b, 0x18, 0x9f, 0x8a, 0x4a, 0x83, 0x96, 0xc6, 0xb3, 0xd7, 0x7d, 0xcf, + 0xac, 0x71, 0x63, 0x91, 0x28, 0x0b, 0x6d, 0x29, 0xff, 0xa9, 0x5c, 0x97, 0x55, 0xdf, 0xf7, 0xe0, + 0xd5, 0xe4, 0xaa, 0xf4, 0xeb, 0xdf, 0xcf, 0xc9, 0x73, 0x9a, 0x58, 0x6a, 0xd6, 0x85, 0xbc, 0xe9, + 0xf9, 0xa6, 0x93, 0x62, 0x89, 0x7f, 0xec, 0x61, 0x0c, 0x56, 0x6a, 0xc6, 0x00, 0x98, 0xb3, 0xa2, + 0x3c, 0xed, 0xa6, 0x69, 0x3f, 0x14, 0xd3, 0xbf, 0x9d, 0x7a, 0xce, 0x95, 0xf3, 0x64, 0x00, 0xcc, + 0x59, 0xa1, 0x7b, 0x90, 0x35, 0xac, 0xfd, 0x94, 0x9e, 0x38, 0x8f, 0xff, 0x9b, 0x00, 0x5e, 0xa8, + 0x51, 0xd9, 0xae, 0x62, 0xca, 0x84, 0xf2, 0xf2, 0x5a, 0xa6, 0xf0, 0x2f, 0x26, 0xe4, 0x55, 0xdb, + 0xd9, 0x4c, 0xe2, 0x55, 0xdb, 0xd9, 0xc4, 0x94, 0x09, 0xfa, 0xa2, 0x06, 0x60, 0xc8, 0x27, 0xfc, + 0xd3, 0x79, 0x93, 0x70, 0xd0, 0xbf, 0x04, 0xe0, 0x15, 0x54, 0x21, 0x14, 0x47, 0x38, 0xeb, 0x6f, + 0x6a, 0xb0, 0xd4, 0x37, 0xd8, 0xf8, 0x7f, 0x37, 0xd0, 0x46, 0xff, 0xef, 0x06, 0xe2, 0xe9, 0x94, + 0x5a, 0xdb, 0x32, 0x13, 0xaf, 0xc9, 0xec, 0xc5, 0xe0, 0xb8, 0xaf, 0x87, 0xfe, 0x2d, 0x0d, 0x8a, + 0x91, 0x12, 0x67, 0xea, 0xf7, 0xb2, 0x52, 0x70, 0x31, 0x8c, 0xf0, 0xd5, 0x18, 0x76, 0x34, 0xc5, + 0x61, 0xfc, 0x94, 0xb4, 0x19, 0x9e, 0x15, 0x46, 0x4e, 0x49, 0x69, 0x2b, 0x16, 0x50, 0x74, 0x19, + 0x72, 0x9e, 0x4f, 0xda, 0x4c, 0xa2, 0x22, 0x15, 0xcf, 0x2c, 0x57, 0xc0, 0x20, 0x8c, 0x1d, 0x55, + 0x8e, 0xa2, 0x7c, 0x25, 0xf2, 0x48, 0x8d, 0x41, 0xdd, 0x6c, 0x06, 0x43, 0x97, 0x20, 0x4b, 0xec, + 0x86, 0xf0, 0x16, 0x8b, 0x02, 0x25, 0x7b, 0xcd, 0x6e, 0x60, 0xda, 0xae, 0xdf, 0x82, 0xd9, 0x1a, + 0xa9, 0xbb, 0xc4, 0x7f, 0x99, 0x1c, 0x8f, 0x76, 0x8e, 0x77, 0x89, 0xe7, 0x3f, 0x33, 0x2a, 0x41, + 0xda, 0x9d, 0xb6, 0xeb, 0x7f, 0xa8, 0x41, 0xec, 0xcd, 0x20, 0xa4, 0xc7, 0x52, 0x9a, 0xd0, 0x9f, + 0xce, 0x54, 0xe2, 0xff, 0xcc, 0xd0, 0xf8, 0x7f, 0x0b, 0x50, 0xcb, 0xf0, 0xeb, 0x87, 0x62, 0x7d, + 0xc4, 0xf3, 0x54, 0xdc, 0x51, 0x0f, 0x2f, 0x54, 0xf4, 0x61, 0xe0, 0x84, 0x5e, 0xfa, 0x12, 0x2c, + 0xc8, 0x28, 0x9e, 0x4b, 0x86, 0xfe, 0xed, 0x2c, 0xcc, 0x2a, 0xaf, 0x43, 0x9f, 0xfc, 0x45, 0x46, + 0x1f, 0x7b, 0x42, 0x34, 0x9e, 0x1d, 0x33, 0x1a, 0x8f, 0x1e, 0x7f, 0xe4, 0xce, 0xf6, 0xf8, 0x23, + 0x9f, 0xce, 0xf1, 0x87, 0x0f, 0xd3, 0xe2, 0xff, 0x91, 0x88, 0x8a, 0xb9, 0x9d, 0x94, 0xae, 0x44, + 0x8a, 0x2b, 0x5e, 0xac, 0x48, 0x30, 0xd8, 0xe5, 0x01, 0x2b, 0xfd, 0x1b, 0x79, 0x98, 0x57, 0x2f, + 0x49, 0x8e, 0xb0, 0x92, 0xef, 0xeb, 0x5b, 0xc9, 0x31, 0x43, 0x9c, 0xec, 0xa4, 0x21, 0x4e, 0x6e, + 0xd2, 0x10, 0x27, 0x7f, 0x8a, 0x10, 0xa7, 0x3f, 0x40, 0x99, 0x1a, 0x39, 0x40, 0xf9, 0xb0, 0x4c, + 0x93, 0x4d, 0x2b, 0xe7, 0xca, 0x61, 0x9a, 0x0c, 0xa9, 0xcb, 0xb0, 0xee, 0x34, 0x12, 0xd3, 0x8d, + 0x85, 0x13, 0x6a, 0xf0, 0xdc, 0xc4, 0xac, 0xd6, 0xf8, 0xa7, 0x28, 0xef, 0x1a, 0x23, 0xa3, 0x15, + 0xfe, 0xcb, 0x1d, 0x66, 0x21, 0x40, 0xb5, 0x2e, 0xb5, 0x10, 0x84, 0xa3, 0x78, 0xec, 0xf9, 0x65, + 0xf5, 0x71, 0x68, 0x16, 0x31, 0x46, 0x9f, 0x5f, 0x8e, 0x3d, 0x26, 0x1d, 0xc7, 0xd7, 0x3f, 0x9b, + 0x81, 0xf0, 0x81, 0x6b, 0xf6, 0x12, 0x95, 0x17, 0x51, 0xd3, 0xc2, 0x99, 0xda, 0x9a, 0xf4, 0xb9, + 0xb7, 0x90, 0xa2, 0x48, 0x48, 0x47, 0x5a, 0xb0, 0xc2, 0xf1, 0xbf, 0xe1, 0x61, 0x6b, 0x03, 0x16, + 0x62, 0x75, 0xb9, 0xa9, 0x17, 0xb8, 0x7c, 0x2b, 0x03, 0x33, 0xb2, 0xb2, 0x99, 0x5a, 0xb6, 0x8e, + 0x1b, 0x3c, 0x9a, 0x23, 0x2d, 0xdb, 0x1d, 0xbc, 0x8d, 0x69, 0x3b, 0x7a, 0x08, 0xd3, 0x87, 0xc4, + 0x68, 0x10, 0x37, 0x38, 0xa7, 0xda, 0x49, 0xa9, 0xa4, 0xfa, 0x06, 0xa3, 0x1a, 0xce, 0x85, 0xff, + 0xf6, 0x70, 0xc0, 0x0e, 0xbd, 0x08, 0xf3, 0xbe, 0xd9, 0x22, 0x34, 0xc0, 0x88, 0x58, 0x8d, 0x6c, + 0x78, 0xf8, 0xb3, 0xa7, 0x40, 0x71, 0x0c, 0x9b, 0xaa, 0xb5, 0x7b, 0x9e, 0x63, 0xb3, 0xfb, 0xc7, + 0x39, 0x35, 0x8a, 0xdb, 0xaa, 0xdd, 0xba, 0xc9, 0xae, 0x1f, 0x4b, 0x0c, 0x8a, 0x6d, 0xb2, 0xca, + 0x4e, 0x97, 0x88, 0x94, 0xd5, 0x62, 0x78, 0x0f, 0x85, 0xb7, 0x63, 0x89, 0xa1, 0xdf, 0x81, 0x85, + 0xd8, 0x44, 0x02, 0x0f, 0x41, 0x4b, 0xf6, 0x10, 0x46, 0xfa, 0xbf, 0x41, 0xd5, 0xd5, 0xb7, 0x7e, + 0xb0, 0xf2, 0xd8, 0x77, 0x7e, 0xb0, 0xf2, 0xd8, 0x77, 0x7f, 0xb0, 0xf2, 0xd8, 0x67, 0x7b, 0x2b, + 0xda, 0x5b, 0xbd, 0x15, 0xed, 0x3b, 0xbd, 0x15, 0xed, 0xbb, 0xbd, 0x15, 0xed, 0xfb, 0xbd, 0x15, + 0xed, 0xcd, 0x1f, 0xae, 0x3c, 0xf6, 0x6a, 0x21, 0xf8, 0x98, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, + 0xfb, 0x73, 0xce, 0x74, 0x36, 0x6d, 0x00, 0x00, } func (m *ALBTrafficRouting) Marshal() (dAtA []byte, err error) { @@ -2911,6 +2944,39 @@ func (m *AnalysisRunStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AnalysisRunStrategy) 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 *AnalysisRunStrategy) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AnalysisRunStrategy) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.UnsuccessfulRunHistoryLimit != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.UnsuccessfulRunHistoryLimit)) + i-- + dAtA[i] = 0x10 + } + if m.SuccessfulRunHistoryLimit != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.SuccessfulRunHistoryLimit)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *AnalysisTemplate) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -5823,6 +5889,18 @@ func (m *RolloutSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Analysis != nil { + { + size, err := m.Analysis.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenerated(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } if m.WorkloadRef != nil { { size, err := m.WorkloadRef.MarshalToSizedBuffer(dAtA[:i]) @@ -6870,6 +6948,21 @@ func (m *AnalysisRunStatus) Size() (n int) { return n } +func (m *AnalysisRunStrategy) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.SuccessfulRunHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.SuccessfulRunHistoryLimit)) + } + if m.UnsuccessfulRunHistoryLimit != nil { + n += 1 + sovGenerated(uint64(*m.UnsuccessfulRunHistoryLimit)) + } + return n +} + func (m *AnalysisTemplate) Size() (n int) { if m == nil { return 0 @@ -7959,6 +8052,10 @@ func (m *RolloutSpec) Size() (n int) { l = m.WorkloadRef.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Analysis != nil { + l = m.Analysis.Size() + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -8368,6 +8465,17 @@ func (this *AnalysisRunStatus) String() string { }, "") return s } +func (this *AnalysisRunStrategy) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AnalysisRunStrategy{`, + `SuccessfulRunHistoryLimit:` + valueToStringGenerated(this.SuccessfulRunHistoryLimit) + `,`, + `UnsuccessfulRunHistoryLimit:` + valueToStringGenerated(this.UnsuccessfulRunHistoryLimit) + `,`, + `}`, + }, "") + return s +} func (this *AnalysisTemplate) String() string { if this == nil { return "nil" @@ -9188,6 +9296,7 @@ func (this *RolloutSpec) String() string { `ProgressDeadlineSeconds:` + valueToStringGenerated(this.ProgressDeadlineSeconds) + `,`, `RestartAt:` + strings.Replace(fmt.Sprintf("%v", this.RestartAt), "Time", "v1.Time", 1) + `,`, `WorkloadRef:` + strings.Replace(this.WorkloadRef.String(), "ObjectRef", "ObjectRef", 1) + `,`, + `Analysis:` + strings.Replace(this.Analysis.String(), "AnalysisRunStrategy", "AnalysisRunStrategy", 1) + `,`, `}`, }, "") return s @@ -10397,6 +10506,96 @@ func (m *AnalysisRunStatus) Unmarshal(dAtA []byte) error { } return nil } +func (m *AnalysisRunStrategy) 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: AnalysisRunStrategy: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AnalysisRunStrategy: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SuccessfulRunHistoryLimit", 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.SuccessfulRunHistoryLimit = &v + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnsuccessfulRunHistoryLimit", 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.UnsuccessfulRunHistoryLimit = &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 *AnalysisTemplate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -19856,6 +20055,42 @@ func (m *RolloutSpec) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Analysis", 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.Analysis == nil { + m.Analysis = &AnalysisRunStrategy{} + } + if err := m.Analysis.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index fc7c59bf94..368d315dc5 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -120,6 +120,16 @@ message AnalysisRunStatus { optional k8s.io.apimachinery.pkg.apis.meta.v1.Time startedAt = 4; } +// AnalysisRunStrategy configuration for the analysis runs and experiments to retain +message AnalysisRunStrategy { + // SuccessfulRunHistoryLimit limits the number of old successful analysis runs and experiments to be retained in a history + optional int32 successfulRunHistoryLimit = 1; + + // UnsuccessfulRunHistoryLimit limits the number of old unsuccessful analysis runs and experiments to be retained in a history. + // Stages for unsuccessful: "Error", "Failed", "Inconclusive" + optional int32 unsuccessfulRunHistoryLimit = 2; +} + // AnalysisTemplate holds the template for performing canary analysis // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -1014,6 +1024,9 @@ message RolloutSpec { // RestartAt indicates when all the pods of a Rollout should be restarted optional k8s.io.apimachinery.pkg.apis.meta.v1.Time restartAt = 9; + + // Analysis configuration for the analysis runs to retain + optional AnalysisRunStrategy analysis = 11; } // RolloutStatus is the status for a Rollout resource diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 29a7aa4dd5..1c990ed785 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -36,6 +36,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunList": schema_pkg_apis_rollouts_v1alpha1_AnalysisRunList(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunSpec": schema_pkg_apis_rollouts_v1alpha1_AnalysisRunSpec(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunStatus": schema_pkg_apis_rollouts_v1alpha1_AnalysisRunStatus(ref), + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunStrategy": schema_pkg_apis_rollouts_v1alpha1_AnalysisRunStrategy(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisTemplate": schema_pkg_apis_rollouts_v1alpha1_AnalysisTemplate(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisTemplateList": schema_pkg_apis_rollouts_v1alpha1_AnalysisTemplateList(ref), "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisTemplateSpec": schema_pkg_apis_rollouts_v1alpha1_AnalysisTemplateSpec(ref), @@ -427,6 +428,33 @@ func schema_pkg_apis_rollouts_v1alpha1_AnalysisRunStatus(ref common.ReferenceCal } } +func schema_pkg_apis_rollouts_v1alpha1_AnalysisRunStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AnalysisRunStrategy configuration for the analysis runs and experiments to retain", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "successfulRunHistoryLimit": { + SchemaProps: spec.SchemaProps{ + Description: "SuccessfulRunHistoryLimit limits the number of old successful analysis runs and experiments to be retained in a history", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "unsuccessfulRunHistoryLimit": { + SchemaProps: spec.SchemaProps{ + Description: "UnsuccessfulRunHistoryLimit limits the number of old unsuccessful analysis runs and experiments to be retained in a history. Stages for unsuccessful: \"Error\", \"Failed\", \"Inconclusive\"", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + func schema_pkg_apis_rollouts_v1alpha1_AnalysisTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2990,11 +3018,17 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutSpec(ref common.ReferenceCallback) Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), }, }, + "analysis": { + SchemaProps: spec.SchemaProps{ + Description: "Analysis configuration for the analysis runs to retain", + Ref: ref("github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunStrategy"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ObjectRef", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutStrategy", "k8s.io/api/core/v1.PodTemplateSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.AnalysisRunStrategy", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.ObjectRef", "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1.RolloutStrategy", "k8s.io/api/core/v1.PodTemplateSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 589fdc821d..bfd2475538 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -71,6 +71,8 @@ type RolloutSpec struct { ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,8,opt,name=progressDeadlineSeconds"` // RestartAt indicates when all the pods of a Rollout should be restarted RestartAt *metav1.Time `json:"restartAt,omitempty" protobuf:"bytes,9,opt,name=restartAt"` + // Analysis configuration for the analysis runs to retain + Analysis *AnalysisRunStrategy `json:"analysis,omitempty" protobuf:"bytes,11,opt,name=analysis"` } func (s *RolloutSpec) SetResolvedSelector(selector *metav1.LabelSelector) { @@ -295,6 +297,15 @@ type CanaryStrategy struct { AbortScaleDownDelaySeconds *int32 `json:"abortScaleDownDelaySeconds,omitempty" protobuf:"varint,13,opt,name=abortScaleDownDelaySeconds"` } +// AnalysisRunStrategy configuration for the analysis runs and experiments to retain +type AnalysisRunStrategy struct { + // SuccessfulRunHistoryLimit limits the number of old successful analysis runs and experiments to be retained in a history + SuccessfulRunHistoryLimit *int32 `json:"successfulRunHistoryLimit,omitempty" protobuf:"varint,1,opt,name=successfulRunHistoryLimit"` + // UnsuccessfulRunHistoryLimit limits the number of old unsuccessful analysis runs and experiments to be retained in a history. + // Stages for unsuccessful: "Error", "Failed", "Inconclusive" + UnsuccessfulRunHistoryLimit *int32 `json:"unsuccessfulRunHistoryLimit,omitempty" protobuf:"varint,2,opt,name=unsuccessfulRunHistoryLimit"` +} + // ALBTrafficRouting configuration for ALB ingress controller to control traffic routing type ALBTrafficRouting struct { // Ingress refers to the name of an `Ingress` resource in the same namespace as the `Rollout` diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index ab24ca447e..aa31789c56 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -202,6 +202,32 @@ func (in *AnalysisRunStatus) DeepCopy() *AnalysisRunStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AnalysisRunStrategy) DeepCopyInto(out *AnalysisRunStrategy) { + *out = *in + if in.SuccessfulRunHistoryLimit != nil { + in, out := &in.SuccessfulRunHistoryLimit, &out.SuccessfulRunHistoryLimit + *out = new(int32) + **out = **in + } + if in.UnsuccessfulRunHistoryLimit != nil { + in, out := &in.UnsuccessfulRunHistoryLimit, &out.UnsuccessfulRunHistoryLimit + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AnalysisRunStrategy. +func (in *AnalysisRunStrategy) DeepCopy() *AnalysisRunStrategy { + if in == nil { + return nil + } + out := new(AnalysisRunStrategy) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AnalysisTemplate) DeepCopyInto(out *AnalysisTemplate) { *out = *in @@ -1621,6 +1647,11 @@ func (in *RolloutSpec) DeepCopyInto(out *RolloutSpec) { in, out := &in.RestartAt, &out.RestartAt *out = (*in).DeepCopy() } + if in.Analysis != nil { + in, out := &in.Analysis, &out.Analysis + *out = new(AnalysisRunStrategy) + (*in).DeepCopyInto(*out) + } return } diff --git a/rollout/analysis.go b/rollout/analysis.go index 74424f45b1..02a944c874 100644 --- a/rollout/analysis.go +++ b/rollout/analysis.go @@ -17,6 +17,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" analysisutil "github.com/argoproj/argo-rollouts/utils/analysis" "github.com/argoproj/argo-rollouts/utils/annotations" + "github.com/argoproj/argo-rollouts/utils/defaults" logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" @@ -131,7 +132,9 @@ func (c *rolloutContext) reconcileAnalysisRuns() error { return err } - arsToDelete := analysisutil.FilterAnalysisRunsToDelete(otherArs, c.allRSs) + limitSucceedArs := defaults.GetAnalysisRunSuccessfulHistoryLimitOrDefault(c.rollout) + limitFailedArs := defaults.GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(c.rollout) + arsToDelete := analysisutil.FilterAnalysisRunsToDelete(otherArs, c.allRSs, limitSucceedArs, limitFailedArs) err = c.deleteAnalysisRuns(arsToDelete) if err != nil { return err diff --git a/rollout/experiment.go b/rollout/experiment.go index 7f21d788e2..a8c8f06f5d 100644 --- a/rollout/experiment.go +++ b/rollout/experiment.go @@ -7,6 +7,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" analysisutil "github.com/argoproj/argo-rollouts/utils/analysis" "github.com/argoproj/argo-rollouts/utils/annotations" + "github.com/argoproj/argo-rollouts/utils/defaults" experimentutil "github.com/argoproj/argo-rollouts/utils/experiment" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" @@ -188,7 +189,9 @@ func (c *rolloutContext) reconcileExperiments() error { return err } - exsToDelete := experimentutil.FilterExperimentsToDelete(otherExs, c.allRSs) + limitSuccessful := defaults.GetAnalysisRunSuccessfulHistoryLimitOrDefault(c.rollout) + limitUnsuccessful := defaults.GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(c.rollout) + exsToDelete := experimentutil.FilterExperimentsToDelete(otherExs, c.allRSs, limitSuccessful, limitUnsuccessful) err = c.deleteExperiments(exsToDelete) if err != nil { return err diff --git a/utils/analysis/filter.go b/utils/analysis/filter.go index 7806fe15ab..ce34f0486f 100644 --- a/utils/analysis/filter.go +++ b/utils/analysis/filter.go @@ -1,6 +1,8 @@ package analysis import ( + "sort" + appsv1 "k8s.io/api/apps/v1" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" @@ -108,6 +110,15 @@ func SortAnalysisRunByPodHash(ars []*v1alpha1.AnalysisRun) map[string][]*v1alpha return podHashToAr } +// AnalysisRunByCreationTimestamp sorts a list of AnalysisRun by creation timestamp +type AnalysisRunByCreationTimestamp []*v1alpha1.AnalysisRun + +func (o AnalysisRunByCreationTimestamp) Len() int { return len(o) } +func (o AnalysisRunByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o AnalysisRunByCreationTimestamp) Less(i, j int) bool { + return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp) +} + // FilterAnalysisRunsToDelete returns a list of analysis runs that should be deleted in the cases where: // 1. The analysis run has no pod hash label, // 2. There is no ReplicaSet with the same pod hash as the analysis run @@ -115,10 +126,10 @@ func SortAnalysisRunByPodHash(ars []*v1alpha1.AnalysisRun) map[string][]*v1alpha // Note: It is okay to use pod hash for filtering since the analysis run's pod hash is originally derived from the new RS. // Even if there is a library change during the lifetime of the analysis run, the ReplicaSet's pod hash that the analysis // run references does not change. -func FilterAnalysisRunsToDelete(ars []*v1alpha1.AnalysisRun, olderRSs []*appsv1.ReplicaSet) []*v1alpha1.AnalysisRun { +func FilterAnalysisRunsToDelete(ars []*v1alpha1.AnalysisRun, allRSs []*appsv1.ReplicaSet, limitSuccessful int32, limitUnsuccessful int32) []*v1alpha1.AnalysisRun { olderRsPodHashes := map[string]bool{} - for i := range olderRSs { - rs := olderRSs[i] + for i := range allRSs { + rs := allRSs[i] if rs == nil { continue } @@ -126,6 +137,10 @@ func FilterAnalysisRunsToDelete(ars []*v1alpha1.AnalysisRun, olderRSs []*appsv1. olderRsPodHashes[podHash] = rs.DeletionTimestamp != nil } } + sort.Sort(sort.Reverse(AnalysisRunByCreationTimestamp(ars))) + + var retainedSuccessful int32 = 0 + var retainedUnsuccessful int32 = 0 arsToDelete := []*v1alpha1.AnalysisRun{} for i := range ars { ar := ars[i] @@ -148,6 +163,22 @@ func FilterAnalysisRunsToDelete(ars []*v1alpha1.AnalysisRun, olderRSs []*appsv1. arsToDelete = append(arsToDelete, ar) continue } + + if ar.Status.Phase == v1alpha1.AnalysisPhaseSuccessful { + if retainedSuccessful < limitSuccessful { + retainedSuccessful++ + } else { + arsToDelete = append(arsToDelete, ar) + } + } else if ar.Status.Phase == v1alpha1.AnalysisPhaseFailed || + ar.Status.Phase == v1alpha1.AnalysisPhaseError || + ar.Status.Phase == v1alpha1.AnalysisPhaseInconclusive { + if retainedUnsuccessful < limitUnsuccessful { + retainedUnsuccessful++ + } else { + arsToDelete = append(arsToDelete, ar) + } + } } return arsToDelete } diff --git a/utils/analysis/filter_test.go b/utils/analysis/filter_test.go index e4240b961e..c7ca00f04b 100644 --- a/utils/analysis/filter_test.go +++ b/utils/analysis/filter_test.go @@ -196,7 +196,7 @@ func TestFilterAnalysisRunsToDelete(t *testing.T) { deletedRS, nil, } - filteredArs := FilterAnalysisRunsToDelete(ars, olderRSs) + filteredArs := FilterAnalysisRunsToDelete(ars, olderRSs, 4, 4) assert.Len(t, filteredArs, 3) assert.NotContains(t, filteredArs, arNoDeletion) assert.Contains(t, filteredArs, arWithNoPodHash) @@ -204,6 +204,59 @@ func TestFilterAnalysisRunsToDelete(t *testing.T) { assert.Contains(t, filteredArs, arWithNoMatchingRS) } +func TestFilterAnalysisRunsToDeleteByLimit(t *testing.T) { + rs := func(podHash string) *appsv1.ReplicaSet { + return &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: podHash}, + }, + } + } + ar := func(podHash string, phase v1alpha1.AnalysisPhase) *v1alpha1.AnalysisRun { + return &v1alpha1.AnalysisRun{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: podHash}, + CreationTimestamp: metav1.Now(), + }, + Status: v1alpha1.AnalysisRunStatus{ + Phase: phase, + }, + } + } + + arS0 := ar("a", v1alpha1.AnalysisPhaseSuccessful) + arS1 := ar("a", v1alpha1.AnalysisPhaseSuccessful) + arS2 := ar("a", v1alpha1.AnalysisPhaseSuccessful) + arS3 := ar("a", v1alpha1.AnalysisPhaseSuccessful) + arS4 := ar("a", v1alpha1.AnalysisPhaseSuccessful) + arF0 := ar("a", v1alpha1.AnalysisPhaseFailed) + arF1 := ar("a", v1alpha1.AnalysisPhaseFailed) + arF2 := ar("a", v1alpha1.AnalysisPhaseFailed) + arF3 := ar("a", v1alpha1.AnalysisPhaseFailed) + arF4 := ar("a", v1alpha1.AnalysisPhaseFailed) + + validRS := rs("a") + ars := []*v1alpha1.AnalysisRun{ + arS0, arF0, arS1, arF1, arS2, arF2, arS3, arF3, arS4, arF4, + } + olderRSs := []*appsv1.ReplicaSet{ + validRS, + nil, + } + + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 1, 0), 9) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 5, 0), 5) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 10, 0), 5) + + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 0, 1), 9) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 0, 5), 5) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 0, 10), 5) + + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 0, 0), 10) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 4, 4), 2) + assert.Len(t, FilterAnalysisRunsToDelete(ars, olderRSs, 10, 10), 0) +} + func TestSortAnalysisRunByPodHash(t *testing.T) { emptyMap := SortAnalysisRunByPodHash(nil) assert.NotNil(t, 0) diff --git a/utils/defaults/defaults.go b/utils/defaults/defaults.go index 99e6520400..cd6e864a11 100644 --- a/utils/defaults/defaults.go +++ b/utils/defaults/defaults.go @@ -16,6 +16,10 @@ const ( DefaultReplicas = int32(1) // DefaultRevisionHistoryLimit default number of revisions to keep if .Spec.RevisionHistoryLimit is nil DefaultRevisionHistoryLimit = int32(10) + // DefaultAnalysisRunSuccessfulHistoryLimit default number of successful AnalysisRuns to keep if .Spec.Analysis.SuccessfulRunHistoryLimit is nil + DefaultAnalysisRunSuccessfulHistoryLimit = int32(5) + // DefaultAnalysisRunUnsuccessfulHistoryLimit default number of unsuccessful AnalysisRuns to keep if .Spec.Analysis.UnsuccessfulRunHistoryLimit is nil + DefaultAnalysisRunUnsuccessfulHistoryLimit = int32(5) // DefaultMaxSurge default number for the max number of additional pods that can be brought up during a rollout DefaultMaxSurge = "25" // DefaultMaxUnavailable default number for the max number of unavailable pods during a rollout @@ -56,6 +60,22 @@ func GetRevisionHistoryLimitOrDefault(rollout *v1alpha1.Rollout) int32 { return *rollout.Spec.RevisionHistoryLimit } +// GetAnalysisRunSuccessfulHistoryLimitOrDefault returns the specified number of succeed AnalysisRuns to keep or the default number +func GetAnalysisRunSuccessfulHistoryLimitOrDefault(rollout *v1alpha1.Rollout) int32 { + if rollout.Spec.Analysis == nil || rollout.Spec.Analysis.SuccessfulRunHistoryLimit == nil { + return DefaultAnalysisRunSuccessfulHistoryLimit + } + return *rollout.Spec.Analysis.SuccessfulRunHistoryLimit +} + +// GetAnalysisRunUnsuccessfulHistoryLimitOrDefault returns the specified number of failed AnalysisRuns to keep or the default number +func GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(rollout *v1alpha1.Rollout) int32 { + if rollout.Spec.Analysis == nil || rollout.Spec.Analysis.UnsuccessfulRunHistoryLimit == nil { + return DefaultAnalysisRunUnsuccessfulHistoryLimit + } + return *rollout.Spec.Analysis.UnsuccessfulRunHistoryLimit +} + func GetMaxSurgeOrDefault(rollout *v1alpha1.Rollout) *intstr.IntOrString { if rollout.Spec.Strategy.Canary != nil && rollout.Spec.Strategy.Canary.MaxSurge != nil { return rollout.Spec.Strategy.Canary.MaxSurge diff --git a/utils/defaults/defaults_test.go b/utils/defaults/defaults_test.go index f76c3b097b..a3e5322c3e 100644 --- a/utils/defaults/defaults_test.go +++ b/utils/defaults/defaults_test.go @@ -45,6 +45,34 @@ func TestGetRevisionHistoryOrDefault(t *testing.T) { assert.Equal(t, DefaultRevisionHistoryLimit, GetRevisionHistoryLimitOrDefault(rolloutDefaultValue)) } +func TestGetAnalysisRunSuccessfulHistoryLimitOrDefault(t *testing.T) { + succeedHistoryLimit := int32(2) + rolloutNonDefaultValue := &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Analysis: &v1alpha1.AnalysisRunStrategy{SuccessfulRunHistoryLimit: &succeedHistoryLimit}, + }, + } + + assert.Equal(t, succeedHistoryLimit, GetAnalysisRunSuccessfulHistoryLimitOrDefault(rolloutNonDefaultValue)) + assert.Equal(t, DefaultAnalysisRunSuccessfulHistoryLimit, GetAnalysisRunSuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{})) + assert.Equal(t, DefaultAnalysisRunSuccessfulHistoryLimit, GetAnalysisRunSuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{Spec: v1alpha1.RolloutSpec{}})) + assert.Equal(t, DefaultAnalysisRunSuccessfulHistoryLimit, GetAnalysisRunSuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{Spec: v1alpha1.RolloutSpec{Analysis: &v1alpha1.AnalysisRunStrategy{}}})) +} + +func TestGetAnalysisRunUnsuccessfulHistoryLimitOrDefault(t *testing.T) { + failedHistoryLimit := int32(3) + rolloutNonDefaultValue := &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Analysis: &v1alpha1.AnalysisRunStrategy{UnsuccessfulRunHistoryLimit: &failedHistoryLimit}, + }, + } + + assert.Equal(t, failedHistoryLimit, GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(rolloutNonDefaultValue)) + assert.Equal(t, DefaultAnalysisRunUnsuccessfulHistoryLimit, GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{})) + assert.Equal(t, DefaultAnalysisRunUnsuccessfulHistoryLimit, GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{Spec: v1alpha1.RolloutSpec{}})) + assert.Equal(t, DefaultAnalysisRunUnsuccessfulHistoryLimit, GetAnalysisRunUnsuccessfulHistoryLimitOrDefault(&v1alpha1.Rollout{Spec: v1alpha1.RolloutSpec{Analysis: &v1alpha1.AnalysisRunStrategy{}}})) +} + func TestGetMaxSurgeOrDefault(t *testing.T) { maxSurge := intstr.FromInt(2) rolloutNonDefaultValue := &v1alpha1.Rollout{ diff --git a/utils/experiment/filter.go b/utils/experiment/filter.go index e75dcc1ee4..a639a13d7b 100644 --- a/utils/experiment/filter.go +++ b/utils/experiment/filter.go @@ -1,6 +1,8 @@ package experiment import ( + "sort" + appsv1 "k8s.io/api/apps/v1" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" @@ -70,7 +72,7 @@ func SortExperimentsByPodHash(exs []*v1alpha1.Experiment) map[string][]*v1alpha1 // Note: It is okay to use pod hash for filtering since the experiments's pod hash is originally derived from the new RS. // Even if there is a library change during the lifetime of the experiments, the ReplicaSet's pod hash that the // experiments references does not change. -func FilterExperimentsToDelete(exs []*v1alpha1.Experiment, olderRSs []*appsv1.ReplicaSet) []*v1alpha1.Experiment { +func FilterExperimentsToDelete(exs []*v1alpha1.Experiment, olderRSs []*appsv1.ReplicaSet, limitSuccessful int32, limitUnsuccessful int32) []*v1alpha1.Experiment { olderRsPodHashes := map[string]bool{} for i := range olderRSs { rs := olderRSs[i] @@ -78,6 +80,10 @@ func FilterExperimentsToDelete(exs []*v1alpha1.Experiment, olderRSs []*appsv1.Re olderRsPodHashes[podHash] = rs.DeletionTimestamp != nil } } + sort.Sort(sort.Reverse(ExperimentByCreationTimestamp(exs))) + + var retainedSuccessful int32 = 0 + var retainedUnsuccessful int32 = 0 exsToDelete := []*v1alpha1.Experiment{} for i := range exs { ex := exs[i] @@ -100,6 +106,22 @@ func FilterExperimentsToDelete(exs []*v1alpha1.Experiment, olderRSs []*appsv1.Re exsToDelete = append(exsToDelete, ex) continue } + + if ex.Status.Phase == v1alpha1.AnalysisPhaseSuccessful { + if retainedSuccessful < limitSuccessful { + retainedSuccessful++ + } else { + exsToDelete = append(exsToDelete, ex) + } + } else if ex.Status.Phase == v1alpha1.AnalysisPhaseFailed || + ex.Status.Phase == v1alpha1.AnalysisPhaseError || + ex.Status.Phase == v1alpha1.AnalysisPhaseInconclusive { + if retainedUnsuccessful < limitUnsuccessful { + retainedUnsuccessful++ + } else { + exsToDelete = append(exsToDelete, ex) + } + } } return exsToDelete } diff --git a/utils/experiment/filter_test.go b/utils/experiment/filter_test.go index 734bb7c841..ae3e28fb72 100644 --- a/utils/experiment/filter_test.go +++ b/utils/experiment/filter_test.go @@ -110,10 +110,62 @@ func TestFilterExperimentsToDelete(t *testing.T) { validRS, deletedRS, } - filteredArs := FilterExperimentsToDelete(exs, olderRSs) + filteredArs := FilterExperimentsToDelete(exs, olderRSs, 5, 5) assert.Len(t, filteredArs, 3) assert.NotContains(t, filteredArs, exNoDeletion) assert.Contains(t, filteredArs, exWithNoPodHash) assert.Contains(t, filteredArs, exWithDeletedRS) assert.Contains(t, filteredArs, exWithNoMatchingRS) } + +func TestFilterExperimentsToDeleteByLimit(t *testing.T) { + rs := func(podHash string) *appsv1.ReplicaSet { + return &appsv1.ReplicaSet{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: podHash}, + }, + } + } + ex := func(podHash string, phase v1alpha1.AnalysisPhase) *v1alpha1.Experiment { + return &v1alpha1.Experiment{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: podHash}, + CreationTimestamp: metav1.Now(), + }, + Status: v1alpha1.ExperimentStatus{ + Phase: phase, + }, + } + } + + arS0 := ex("a", v1alpha1.AnalysisPhaseSuccessful) + arS1 := ex("a", v1alpha1.AnalysisPhaseSuccessful) + arS2 := ex("a", v1alpha1.AnalysisPhaseSuccessful) + arS3 := ex("a", v1alpha1.AnalysisPhaseSuccessful) + arS4 := ex("a", v1alpha1.AnalysisPhaseSuccessful) + arF0 := ex("a", v1alpha1.AnalysisPhaseFailed) + arF1 := ex("a", v1alpha1.AnalysisPhaseError) + arF2 := ex("a", v1alpha1.AnalysisPhaseInconclusive) + arF3 := ex("a", v1alpha1.AnalysisPhaseFailed) + arF4 := ex("a", v1alpha1.AnalysisPhaseFailed) + + validRS := rs("a") + ars := []*v1alpha1.Experiment{ + arS0, arF0, arS1, arF1, arS2, arF2, arS3, arF3, arS4, arF4, + } + olderRSs := []*appsv1.ReplicaSet{ + validRS, + } + + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 1, 0), 9) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 5, 0), 5) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 10, 0), 5) + + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 0, 1), 9) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 0, 5), 5) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 0, 10), 5) + + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 0, 0), 10) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 4, 4), 2) + assert.Len(t, FilterExperimentsToDelete(ars, olderRSs, 10, 10), 0) +} From 7a0704a841ba1fb6112ee180d4da48edfe1d13c7 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Fri, 6 Aug 2021 03:15:18 -0700 Subject: [PATCH 07/34] fix: retarget blue-green previewService before scaling up preview ReplicaSet (#1368) Signed-off-by: Jesse Suen --- docs/features/bluegreen.md | 29 ++++++++++++++++++++--------- rollout/analysis_test.go | 3 +++ rollout/bluegreen.go | 11 ++++++----- rollout/bluegreen_test.go | 1 + rollout/canary_test.go | 13 +++++++++++-- rollout/ephemeralmetadata_test.go | 5 ++++- rollout/experiment_test.go | 1 + rollout/sync.go | 14 +++----------- rollout/sync_test.go | 7 +++++-- test/e2e/functional_test.go | 12 ++++++++---- utils/conditions/conditions.go | 2 +- 11 files changed, 63 insertions(+), 35 deletions(-) diff --git a/docs/features/bluegreen.md b/docs/features/bluegreen.md index 95e2ce4241..afb2d2f158 100644 --- a/docs/features/bluegreen.md +++ b/docs/features/bluegreen.md @@ -68,6 +68,25 @@ spec: scaleDownDelayRevisionLimit: *int32 ``` +## Sequence of Events + +The following describes the sequence of events that happen during a blue-green update. + +1. Beginning at a fully promoted, steady-state, a revision 1 ReplicaSet is pointed to by both the `activeService` and `previewService`. +1. A user initiates an update by modifying the pod template (`spec.template.spec`). +1. The revision 2 ReplicaSet is created with size 0. +1. The preview service is modified to point to the revision 2 ReplicaSet. The `activeService` remains pointing to revision 1. +1. The revision 2 ReplicaSet is scaled to either `spec.replicas` or `previewReplicaCount` if set. +1. Once revision 2 ReplicaSet Pods are fully available, `prePromotionAnalysis` begins. +1. Upon success of `prePromotionAnalysis`, the blue/green pauses if `autoPromotionEnabled` is false, or `autoPromotionSeconds` is non-zero. +1. The rollout is resumed either manually by a user, or automatically by surpassing `autoPromotionSeconds`. +1. The revision 2 ReplicaSet is scaled to the `spec.replicas`, if the `previewReplicaCount` feature was used. +1. The rollout "promotes" the revision 2 ReplicaSet by updating the `activeService` to point to it. At this point, there are no services pointing to revision 1 +1. `postPromotionAnalysis` analysis begins +1. Once `postPromotionAnalysis` completes successfully, the update is successful and the revision 2 ReplicaSet is marked as stable. The rollout is considered fully-promoted. +1. After waiting `scaleDownDelaySeconds` (default 30 seconds), the revision 1 ReplicaSet is scaled down + + ### autoPromotionEnabled The AutoPromotionEnabled will make the rollout automatically promote the new ReplicaSet to the active service once the new ReplicaSet is healthy. This field is defaulted to true if it is not specified. @@ -111,15 +130,6 @@ This feature is used to provide an endpoint that can be used to test a new versi Defaults to an empty string -Here is a timeline of how the active and preview services work (if you use a preview service): - -1. During the Initial deployment there is only one ReplicaSet. Both active and preview services point to it. This is the **old** version of the application. -1. A change happens in the Rollout resource. A new ReplicaSet is created. This is the **new** version of the application. The preview service is modified to point to the new ReplicaSet. The active service still points to the old version. -1. The blue/green deployment is "promoted". Both active and preview services are pointing to the new version. The old version is still there but no service is pointing at it. -1. Once the the blue/green deployment is scaled down (see the `scaleDownDelaySeconds` field) the old ReplicaSet is has 0 replicas and we are back to the initial state. Both active and preview services point to the new version (which is the only one present anyway) - - - ### previewReplicaCount The PreviewReplicaCount field will indicate the number of replicas that the new version of an application should run. Once the application is ready to promote to the active service, the controller will scale the new ReplicaSet to the value of the `spec.replicas`. The rollout will not switch over the active service to the new ReplicaSet until it matches the `spec.replicas` count. @@ -136,3 +146,4 @@ Defaults to 30 The ScaleDownDelayRevisionLimit limits the number of old active ReplicaSets to keep scaled up while they wait for the scaleDownDelay to pass after being removed from the active service. If omitted, all ReplicaSets will be retained for the specified scaleDownDelay + diff --git a/rollout/analysis_test.go b/rollout/analysis_test.go index 8e665ed2f7..e32aa5e982 100644 --- a/rollout/analysis_test.go +++ b/rollout/analysis_test.go @@ -1517,6 +1517,7 @@ func TestDoNotCreateBackgroundAnalysisRunOnNewCanaryRollout(t *testing.T) { f.expectCreateReplicaSetAction(rs1) f.expectUpdateRolloutStatusAction(r1) // update conditions + f.expectUpdateReplicaSetAction(rs1) // scale replica set f.expectPatchRolloutAction(r1) f.run(getKey(r1, t)) } @@ -1551,6 +1552,7 @@ func TestDoNotCreateBackgroundAnalysisRunOnNewCanaryRolloutStableRSEmpty(t *test f.expectCreateReplicaSetAction(rs1) f.expectUpdateRolloutStatusAction(r1) // update conditions + f.expectUpdateReplicaSetAction(rs1) // scale replica set f.expectPatchRolloutAction(r1) f.run(getKey(r1, t)) } @@ -1686,6 +1688,7 @@ func TestDoNotCreatePrePromotionAnalysisRunOnNewRollout(t *testing.T) { f.expectCreateReplicaSetAction(rs) f.expectUpdateRolloutStatusAction(r) + f.expectUpdateReplicaSetAction(rs) // scale RS f.expectPatchRolloutAction(r) f.run(getKey(r, t)) } diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index fd8ab46848..8789666b41 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -25,6 +25,12 @@ func (c *rolloutContext) rolloutBlueGreen() error { return err } + // This must happen right after the new replicaset is created + err = c.reconcilePreviewService(previewSvc) + if err != nil { + return err + } + if replicasetutil.CheckPodSpecChange(c.rollout, c.newRS) { return c.syncRolloutStatusBlueGreen(previewSvc, activeSvc) } @@ -39,11 +45,6 @@ func (c *rolloutContext) rolloutBlueGreen() error { return err } - err = c.reconcilePreviewService(previewSvc) - if err != nil { - return err - } - c.reconcileBlueGreenPause(activeSvc, previewSvc) err = c.reconcileActiveService(activeSvc) diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index 500343a521..25aa70d46c 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -56,6 +56,7 @@ func TestBlueGreenCreatesReplicaSet(t *testing.T) { f.expectCreateReplicaSetAction(rs) servicePatchIndex := f.expectPatchServiceAction(previewSvc, rsPodHash) + f.expectUpdateReplicaSetAction(rs) // scale up RS updatedRolloutIndex := f.expectUpdateRolloutStatusAction(r) expectedPatchWithoutSubs := `{ "status":{ diff --git a/rollout/canary_test.go b/rollout/canary_test.go index ace03f0ea9..bb7d4cae23 100644 --- a/rollout/canary_test.go +++ b/rollout/canary_test.go @@ -77,15 +77,19 @@ func TestCanaryRolloutBumpVersion(t *testing.T) { f.replicaSetLister = append(f.replicaSetLister, rs1) createdRSIndex := f.expectCreateReplicaSetAction(rs2) + updatedRSIndex := f.expectUpdateReplicaSetAction(rs2) // scale up RS updatedRolloutRevisionIndex := f.expectUpdateRolloutAction(r2) // update rollout revision updatedRolloutConditionsIndex := f.expectUpdateRolloutStatusAction(r2) // update rollout conditions f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) createdRS := f.getCreatedReplicaSet(createdRSIndex) - assert.Equal(t, int32(1), *createdRS.Spec.Replicas) + assert.Equal(t, int32(0), *createdRS.Spec.Replicas) assert.Equal(t, "2", createdRS.Annotations[annotations.RevisionAnnotation]) + updatedRS := f.getUpdatedReplicaSet(updatedRSIndex) + assert.Equal(t, int32(1), *updatedRS.Spec.Replicas) + updatedRollout := f.getUpdatedRollout(updatedRolloutRevisionIndex) assert.Equal(t, "2", updatedRollout.Annotations[annotations.RevisionAnnotation]) @@ -475,6 +479,7 @@ func TestCanaryRolloutCreateFirstReplicasetNoSteps(t *testing.T) { rs := newReplicaSet(r, 1) f.expectCreateReplicaSetAction(rs) + f.expectUpdateReplicaSetAction(rs) // scale up rs updatedRolloutIndex := f.expectUpdateRolloutStatusAction(r) patchIndex := f.expectPatchRolloutAction(r) f.run(getKey(r, t)) @@ -514,6 +519,7 @@ func TestCanaryRolloutCreateFirstReplicasetWithSteps(t *testing.T) { rs := newReplicaSet(r, 1) f.expectCreateReplicaSetAction(rs) + f.expectUpdateReplicaSetAction(rs) // scale up rs updatedRolloutIndex := f.expectUpdateRolloutStatusAction(r) patchIndex := f.expectPatchRolloutAction(r) f.run(getKey(r, t)) @@ -559,12 +565,15 @@ func TestCanaryRolloutCreateNewReplicaWithCorrectWeight(t *testing.T) { f.replicaSetLister = append(f.replicaSetLister, rs1) createdRSIndex := f.expectCreateReplicaSetAction(rs2) + updatedRSIndex := f.expectUpdateReplicaSetAction(rs2) updatedRolloutIndex := f.expectUpdateRolloutStatusAction(r2) f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) createdRS := f.getCreatedReplicaSet(createdRSIndex) - assert.Equal(t, int32(1), *createdRS.Spec.Replicas) + assert.Equal(t, int32(0), *createdRS.Spec.Replicas) + updatedRS := f.getUpdatedReplicaSet(updatedRSIndex) + assert.Equal(t, int32(1), *updatedRS.Spec.Replicas) updatedRollout := f.getUpdatedRollout(updatedRolloutIndex) progressingCondition := conditions.GetRolloutCondition(updatedRollout.Status, v1alpha1.RolloutProgressing) diff --git a/rollout/ephemeralmetadata_test.go b/rollout/ephemeralmetadata_test.go index 7e9d4b850d..a68e4f39d2 100644 --- a/rollout/ephemeralmetadata_test.go +++ b/rollout/ephemeralmetadata_test.go @@ -37,6 +37,7 @@ func TestSyncCanaryEphemeralMetadataInitialRevision(t *testing.T) { f.expectUpdateRolloutStatusAction(r1) idx := f.expectCreateReplicaSetAction(rs1) + f.expectUpdateReplicaSetAction(rs1) _ = f.expectPatchRolloutAction(r1) f.run(getKey(r1, t)) createdRS1 := f.getCreatedReplicaSet(idx) @@ -75,8 +76,9 @@ func TestSyncBlueGreenEphemeralMetadataInitialRevision(t *testing.T) { f.expectUpdateRolloutStatusAction(r1) idx := f.expectCreateReplicaSetAction(rs1) - _ = f.expectPatchRolloutAction(r1) + f.expectPatchRolloutAction(r1) f.expectPatchServiceAction(previewSvc, rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]) + f.expectUpdateReplicaSetAction(rs1) // scale replicaset f.run(getKey(r1, t)) createdRS1 := f.getCreatedReplicaSet(idx) expectedLabels := map[string]string{ @@ -209,6 +211,7 @@ func TestSyncBlueGreenEphemeralMetadataSecondRevision(t *testing.T) { f.expectUpdateRolloutStatusAction(r2) // Update Rollout conditions rs2idx := f.expectCreateReplicaSetAction(rs2) // Create revision 2 ReplicaSet f.expectPatchServiceAction(previewSvc, rs2PodHash) // Update preview service to point at revision 2 replicaset + f.expectUpdateReplicaSetAction(rs2) // scale revision 2 ReplicaSet up f.expectListPodAction(r1.Namespace) // list pods to patch ephemeral data on revision 1 ReplicaSets pods` podIdx := f.expectUpdatePodAction(&pod) // Update pod with ephemeral data rs1idx := f.expectUpdateReplicaSetAction(rs1) // update stable replicaset with stable metadata diff --git a/rollout/experiment_test.go b/rollout/experiment_test.go index 32159734fb..18277d3640 100644 --- a/rollout/experiment_test.go +++ b/rollout/experiment_test.go @@ -519,6 +519,7 @@ func TestRolloutDoNotCreateExperimentWithoutStableRS(t *testing.T) { f.expectCreateReplicaSetAction(rs2) f.expectUpdateRolloutAction(r2) // update revision f.expectUpdateRolloutStatusAction(r2) // update progressing condition + f.expectUpdateReplicaSetAction(rs2) // scale replicaset f.expectPatchRolloutAction(r1) f.run(getKey(r2, t)) } diff --git a/rollout/sync.go b/rollout/sync.go index d541ece498..27f9872195 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -159,13 +159,7 @@ func (c *rolloutContext) createDesiredReplicaSet() (*appsv1.ReplicaSet, error) { Template: newRSTemplate, }, } - allRSs := append(c.allRSs, newRS) - newReplicasCount, err := replicasetutil.NewRSNewReplicas(c.rollout, allRSs, newRS) - if err != nil { - return nil, err - } - - newRS.Spec.Replicas = pointer.Int32Ptr(newReplicasCount) + newRS.Spec.Replicas = pointer.Int32Ptr(0) // Set new replica set's annotation annotations.SetNewReplicaSetAnnotations(c.rollout, newRS, newRevision, false) @@ -250,12 +244,10 @@ func (c *rolloutContext) createDesiredReplicaSet() (*appsv1.ReplicaSet, error) { return nil, err } - if !alreadyExists && newReplicasCount > 0 { + if !alreadyExists { revision, _ := replicasetutil.Revision(createdRS) - c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.NewReplicaSetReason}, conditions.NewReplicaSetDetailedMessage, createdRS.Name, revision, newReplicasCount) - } + c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.NewReplicaSetReason}, conditions.NewReplicaSetDetailedMessage, createdRS.Name, revision) - if !alreadyExists { msg := fmt.Sprintf(conditions.NewReplicaSetMessage, createdRS.Name) condition := conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionTrue, conditions.NewReplicaSetReason, msg) conditions.SetRolloutCondition(&c.rollout.Status, *condition) diff --git a/rollout/sync_test.go b/rollout/sync_test.go index 61902f713b..ba3eb6f2c5 100644 --- a/rollout/sync_test.go +++ b/rollout/sync_test.go @@ -304,14 +304,17 @@ func TestCanaryPromoteFull(t *testing.T) { f.kubeobjects = append(f.kubeobjects, rs1) f.replicaSetLister = append(f.replicaSetLister, rs1) - createdRS2Index := f.expectCreateReplicaSetAction(rs2) // create new ReplicaSet (surge to 10) + createdRS2Index := f.expectCreateReplicaSetAction(rs2) // create new ReplicaSet (size 0) f.expectUpdateRolloutAction(r2) // update rollout revision f.expectUpdateRolloutStatusAction(r2) // update rollout conditions + updatedRS2Index := f.expectUpdateReplicaSetAction(rs2) // scale new ReplicaSet to 10 patchedRolloutIndex := f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) createdRS2 := f.getCreatedReplicaSet(createdRS2Index) - assert.Equal(t, int32(10), *createdRS2.Spec.Replicas) // verify we ignored steps + assert.Equal(t, int32(0), *createdRS2.Spec.Replicas) + updatedRS2 := f.getUpdatedReplicaSet(updatedRS2Index) + assert.Equal(t, int32(10), *updatedRS2.Spec.Replicas) // verify we ignored steps and fully scaled it patchedRollout := f.getPatchedRolloutAsObject(patchedRolloutIndex) assert.Equal(t, int32(2), *patchedRollout.Status.CurrentStepIndex) // verify we updated to last step diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index fc381773f3..1c07a73620 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -90,10 +90,12 @@ spec: ExpectRevisionPodCount("2", 1). ExpectRolloutEvents([]string{ "RolloutUpdated", // Rollout updated to revision 1 - "NewReplicaSetCreated", // Created ReplicaSet abort-retry-promote-698fbfb9dc (revision 1) with size 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 "RolloutUpdated", // Rollout updated to revision 2 - "NewReplicaSetCreated", // Created ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) with size 1 + "NewReplicaSetCreated", // Created ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) + "ScalingReplicaSet", // Scaled up ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) from 0 to 1 "RolloutStepCompleted", // Rollout step 1/2 completed (setWeight: 50) "RolloutPaused", // Rollout is paused (CanaryPauseStep) "ScalingReplicaSet", // Scaled down ReplicaSet abort-retry-promote-75dcb5ddd6 (revision 2) from 1 to 0 @@ -696,11 +698,13 @@ func (s *FunctionalSuite) TestBlueGreenUpdate() { ExpectReplicaCounts(3, 6, 3, 3, 3). ExpectRolloutEvents([]string{ "RolloutUpdated", // Rollout updated to revision 1 - "NewReplicaSetCreated", // Created ReplicaSet bluegreen-7dcd8f8869 (revision 1) with size 3 + "NewReplicaSetCreated", // Created ReplicaSet bluegreen-7dcd8f8869 (revision 1) + "ScalingReplicaSet", // Scaled up ReplicaSet bluegreen-7dcd8f8869 (revision 1) from 0 to 3 "RolloutCompleted", // Rollout completed update to revision 1 (7dcd8f8869): Initial deploy "SwitchService", // Switched selector for service 'bluegreen' from '' to '7dcd8f8869' "RolloutUpdated", // Rollout updated to revision 2 - "NewReplicaSetCreated", // Created ReplicaSet bluegreen-5498785cd6 (revision 2) with size 3 + "NewReplicaSetCreated", // Created ReplicaSet bluegreen-5498785cd6 (revision 2) + "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 }) diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index c65d8f4734..5826db3f89 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -52,7 +52,7 @@ const ( //NewReplicaSetMessage is added in a rollout when it creates a new replicas set. NewReplicaSetMessage = "Created new replica set %q" // NewReplicaSetDetailedMessage is a more detailed format message - NewReplicaSetDetailedMessage = "Created ReplicaSet %s (revision %d) with size %d" + NewReplicaSetDetailedMessage = "Created ReplicaSet %s (revision %d)" // FoundNewRSReason is added in a rollout when it adopts an existing replica set. FoundNewRSReason = "FoundNewReplicaSet" From cf19b5b88089adcf4cc0b493de2db232cfb9181c Mon Sep 17 00:00:00 2001 From: dabaooline <201028369@qq.com> Date: Fri, 6 Aug 2021 18:19:37 +0800 Subject: [PATCH 08/34] docs: add custom namespace name tips (#1354) --- docs/installation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 85074936bd..3456ab6507 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -9,6 +9,9 @@ kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/rele This will create a new namespace, `argo-rollouts`, where Argo Rollouts controller will run. +!!! tip + If you are using another namspace name, please update `install.yall` clusterrolebinding's serviceaccount namespace name. + !!! tip When installing Argo Rollouts on Kubernetes v1.14 or lower, the CRD manifests must be kubectl applied with the --validate=false option. This is caused by use of new CRD fields introduced in v1.15, which are rejected by default in lower API servers. From 86107dee018d6a4c1f9573e7137479c55da57364 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Thu, 12 Aug 2021 04:16:46 -0400 Subject: [PATCH 09/34] feat: add support for Istio VirtualService spec.tls[] (#1380) Signed-off-by: Rohit Agrawal --- docs/getting-started/istio/index.md | 62 +- manifests/crds/rollout-crd.yaml | 12 + manifests/install.yaml | 12 + manifests/namespace-install.yaml | 12 + pkg/apiclient/rollout/rollout.swagger.json | 27 +- pkg/apis/api-rules/violation_exceptions.list | 2 + pkg/apis/rollouts/v1alpha1/types.go | 12 +- .../v1alpha1/zz_generated.deepcopy.go | 28 + rollout/trafficrouting/istio/istio.go | 347 ++++++-- rollout/trafficrouting/istio/istio_test.go | 741 ++++++++++++++++-- rollout/trafficrouting/istio/istio_types.go | 28 +- utils/evaluate/evaluate.go | 17 + utils/evaluate/evaluate_test.go | 6 + 13 files changed, 1132 insertions(+), 174 deletions(-) diff --git a/docs/getting-started/istio/index.md b/docs/getting-started/istio/index.md index d9a0bcc778..03d965069a 100644 --- a/docs/getting-started/istio/index.md +++ b/docs/getting-started/istio/index.md @@ -1,7 +1,7 @@ # Getting Started - Istio -This guide covers how Argo Rollouts integrates with the [Istio Service Mesh](https://istio.io/) -for traffic shaping. +This guide covers how Argo Rollouts integrates with the [Istio Service Mesh](https://istio.io/) +for traffic shaping. This guide builds upon the concepts of the [basic getting started guide](../../getting-started.md). ## Requirements @@ -33,15 +33,28 @@ spec: virtualService: # Reference to a VirtualService which the controller updates with canary weights name: rollouts-demo-vsvc + # Optional if there is a single HTTP route in the VirtualService, otherwise required routes: - - primary # optional if there is a single route in VirtualService, required otherwise + - http-primary + # Optional if there is a single HTTPS/TLS route in the VirtualService, otherwise required + tlsRoutes: + # Below fields are optional but if defined, they should match exactly with at least one of the TLS route match rules in your VirtualService + - port: 443 # Only required if you want to match any rule in your VirtualService which contains this port + # Only required if you want to match any rule in your VirtualService which contain all these SNI hosts + sniHosts: + - reviews.bookinfo.com + - localhost ... ``` The VirtualService and route referenced in `trafficRouting.istio.virtualService` is required -to have a HTTP route which splits between the stable and canary Services, referenced in the rollout. -In this guide, those Services are named: `rollouts-demo-stable` and `rollouts-demo-canary` -respectively. The weight values for these services used should be initially set to 100% stable, +to have either an HTTP or a TLS route spec that splits between the stable and the canary services, +referenced in the rollout. If the route is of type HTTPS/TLS, then we can match it based on the +given port number and/or SNI hosts. Note that both of them are optional and only needed if you +want to match any rule in your VirtualService which contain these. + +In this guide, the two services are named: `rollouts-demo-stable` and `rollouts-demo-canary` +respectively. The weight values for these services used should be initially set to 100% stable, and 0% on the canary. During an update, these values will be modified by the controller. ```yaml @@ -55,7 +68,20 @@ spec: hosts: - rollouts-demo.local http: - - name: primary # Should match spec.strategy.canary.trafficRouting.istio.virtualService.routes + - name: http-primary # Should match spec.strategy.canary.trafficRouting.istio.virtualService.routes + route: + - destination: + host: rollouts-demo-stable # Should match spec.strategy.canary.stableService + weight: 100 + - destination: + host: rollouts-demo-canary # Should match spec.strategy.canary.canaryService + weight: 0 + tls: + - match: + - port: 3000 # Should match the port number of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes + sniHosts: # Should match all the SNI hosts of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes + - reviews.bookinfo.com + - localhost route: - destination: host: rollouts-demo-stable # Should match spec.strategy.canary.stableService @@ -63,7 +89,6 @@ spec: - destination: host: rollouts-demo-canary # Should match spec.strategy.canary.canaryService weight: 0 - ``` Run the following commands to deploy: @@ -80,7 +105,7 @@ kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master kubectl apply -f https://raw.githubusercontent.com/argoproj/argo-rollouts/master/docs/getting-started/istio/gateway.yaml ``` -After applying the manifests you should see the following rollout, services, virtualservices, +After applying the manifests you should see the following rollout, services, virtualservices, and gateway resources in the cluster: ```shell @@ -137,7 +162,20 @@ spec: hosts: - rollouts-demo.local http: - - name: primary + - name: http-primary + route: + - destination: + host: rollouts-demo-stable + weight: 95 + - destination: + host: rollouts-demo-canary + weight: 5 + tls: + - match: + - port: 3000 + sniHosts: + - reviews.bookinfo.com + - localhost route: - destination: host: rollouts-demo-stable @@ -147,5 +185,5 @@ spec: weight: 5 ``` -As the Rollout progresses through steps, the HTTP route destination weights will be adjusted to -match the current setWeight of the steps. +As the Rollout progresses through steps, the HTTP and/or TLS route(s) destination weights will be +adjusted to match the current `setWeight` of the steps. diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 6c5cb0a944..cdc7d557ec 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -538,6 +538,18 @@ spec: items: type: string type: array + tlsRoutes: + items: + properties: + port: + format: int64 + type: integer + sniHosts: + items: + type: string + type: array + type: object + type: array required: - name type: object diff --git a/manifests/install.yaml b/manifests/install.yaml index 8c6b321ab2..1961544028 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -10236,6 +10236,18 @@ spec: items: type: string type: array + tlsRoutes: + items: + properties: + port: + format: int64 + type: integer + sniHosts: + items: + type: string + type: array + type: object + type: array required: - name type: object diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 2fc1caf4b0..428dfc0515 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -10236,6 +10236,18 @@ spec: items: type: string type: array + tlsRoutes: + items: + properties: + port: + format: int64 + type: integer + sniHosts: + items: + type: string + type: array + type: object + type: array required: - name type: object diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 7657b2867c..117b49073b 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -849,7 +849,14 @@ "items": { "type": "string" }, - "title": "Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route" + "description": "A list of HTTP routes within VirtualService to edit. If omitted, VirtualService must have a single route of this type." + }, + "tlsRoutes": { + "type": "array", + "items": { + "$ref": "#/definitions/github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.TLSRoute" + }, + "description": "A list of TLS/HTTPS routes within VirtualService to edit. If omitted, VirtualService must have a single route of this type." } }, "title": "IstioVirtualService holds information on the virtual service the rollout needs to modify" @@ -1378,6 +1385,24 @@ }, "title": "SetCanaryScale defines how to scale the newRS without changing traffic weight" }, + "github.com.argoproj.argo_rollouts.pkg.apis.rollouts.v1alpha1.TLSRoute": { + "type": "object", + "properties": { + "port": { + "type": "string", + "format": "int64", + "description": "Port number of the TLS Route desired to be matched in the given Istio VirtualService." + }, + "sniHosts": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of all the SNI Hosts of the TLS Route desired to be matched in the given Istio VirtualService." + } + }, + "description": "TLSRoute holds the information on the virtual service's TLS/HTTPS routes that are desired to be matched for changing weights." + }, "google.protobuf.Any": { "type": "object", "properties": { diff --git a/pkg/apis/api-rules/violation_exceptions.list b/pkg/apis/api-rules/violation_exceptions.list index 63d5e8fe27..5ca2d709d9 100644 --- a/pkg/apis/api-rules/violation_exceptions.list +++ b/pkg/apis/api-rules/violation_exceptions.list @@ -12,6 +12,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,ExperimentStatus,Conditions API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,ExperimentStatus,TemplateStatuses API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,IstioVirtualService,Routes +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,RolloutAnalysis,Args @@ -21,5 +22,6 @@ 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,TLSRoute,SNIHosts API rule violation: list_type_missing,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,WebMetric,Headers API rule violation: names_match,github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1,RolloutStatus,HPAReplicas diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index bfd2475538..193c7855c5 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -374,8 +374,18 @@ type IstioTrafficRouting struct { type IstioVirtualService struct { // Name holds the name of the VirtualService Name string `json:"name" protobuf:"bytes,1,opt,name=name"` - // Routes are list of routes within VirtualService to edit. If omitted, VirtualService must have a single route + // A list of HTTP routes within VirtualService to edit. If omitted, VirtualService must have a single route of this type. Routes []string `json:"routes,omitempty" protobuf:"bytes,2,rep,name=routes"` + // A list of TLS/HTTPS routes within VirtualService to edit. If omitted, VirtualService must have a single route of this type. + TLSRoutes []TLSRoute `json:"tlsRoutes,omitempty" protobuf:"bytes,3,rep,name=tlsRoutes"` +} + +// TLSRoute holds the information on the virtual service's TLS/HTTPS routes that are desired to be matched for changing weights. +type TLSRoute struct { + // Port number of the TLS Route desired to be matched in the given Istio VirtualService. + Port int64 `json:"port,omitempty" protobuf:"bytes,1,opt,name=port"` + // A list of all the SNI Hosts of the TLS Route desired to be matched in the given Istio VirtualService. + SNIHosts []string `json:"sniHosts,omitempty" protobuf:"bytes,2,rep,name=sniHosts"` } // IstioDestinationRule is a reference to an Istio DestinationRule to modify and shape traffic diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index aa31789c56..5f45a8e837 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -968,6 +968,13 @@ func (in *IstioVirtualService) DeepCopyInto(out *IstioVirtualService) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.TLSRoutes != nil { + in, out := &in.TLSRoutes, &out.TLSRoutes + *out = make([]TLSRoute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } return } @@ -1856,6 +1863,27 @@ func (in *SetCanaryScale) DeepCopy() *SetCanaryScale { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSRoute) DeepCopyInto(out *TLSRoute) { + *out = *in + if in.SNIHosts != nil { + in, out := &in.SNIHosts, &out.SNIHosts + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSRoute. +func (in *TLSRoute) DeepCopy() *TLSRoute { + if in == nil { + return nil + } + out := new(TLSRoute) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TemplateService) DeepCopyInto(out *TemplateService) { *out = *in diff --git a/rollout/trafficrouting/istio/istio.go b/rollout/trafficrouting/istio/istio.go index 6119f20d88..c6298a43c2 100644 --- a/rollout/trafficrouting/istio/istio.go +++ b/rollout/trafficrouting/istio/istio.go @@ -16,11 +16,14 @@ import ( "k8s.io/client-go/dynamic/dynamiclister" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + evalUtils "github.com/argoproj/argo-rollouts/utils/evaluate" istioutil "github.com/argoproj/argo-rollouts/utils/istio" logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" ) +const Http = "http" +const Tls = "tls" const Type = "Istio" // NewReconciler returns a reconciler struct that brings the Virtual Service into the desired state @@ -48,39 +51,57 @@ type Reconciler struct { type virtualServicePatch struct { routeIndex int + routeType string destinationIndex int weight int64 } + type virtualServicePatches []virtualServicePatch +type svcSubsets struct { + canarySvc string + stableSvc string + canarySubset string + stableSubset string +} + const ( invalidCasting = "Invalid casting: field '%s' is not of type '%s'" ) -func (patches virtualServicePatches) patchVirtualService(httpRoutes []interface{}) error { +func (patches virtualServicePatches) patchVirtualService(httpRoutes []interface{}, tlsRoutes []interface{}) error { for _, patch := range patches { - route, ok := httpRoutes[patch.routeIndex].(map[string]interface{}) - if !ok { - return fmt.Errorf(invalidCasting, "http[]", "map[string]interface") + var route map[string]interface{} + err := false + if patch.routeType == Http { + route, err = httpRoutes[patch.routeIndex].(map[string]interface{}) + } else if patch.routeType == Tls { + route, err = tlsRoutes[patch.routeIndex].(map[string]interface{}) + } + if !err { + return fmt.Errorf(invalidCasting, patch.routeType+"[]", "map[string]interface") } destinations, ok := route["route"].([]interface{}) if !ok { - return fmt.Errorf(invalidCasting, "http[].route", "[]interface") + return fmt.Errorf(invalidCasting, patch.routeType+"[].route", "[]interface") } destination, ok := destinations[patch.destinationIndex].(map[string]interface{}) if !ok { - return fmt.Errorf(invalidCasting, "http[].route[].destination", "map[string]interface") + return fmt.Errorf(invalidCasting, patch.routeType+"[].route[].destination", "map[string]interface") } destination["weight"] = float64(patch.weight) - destinations[patch.destinationIndex] = destination route["route"] = destinations - httpRoutes[patch.routeIndex] = route + if patch.routeType == Http { + httpRoutes[patch.routeIndex] = route + } else if patch.routeType == Tls { + tlsRoutes[patch.routeIndex] = route + } } return nil } -func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHTTPRoute, desiredWeight int64) virtualServicePatches { +func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHTTPRoute, tlsRoutes []VirtualServiceTLSRoute, desiredWeight int64) virtualServicePatches { canarySvc := r.rollout.Spec.Strategy.Canary.CanaryService stableSvc := r.rollout.Spec.Strategy.Canary.StableService canarySubset := "" @@ -91,70 +112,113 @@ func (r *Reconciler) generateVirtualServicePatches(httpRoutes []VirtualServiceHT } // err can be ignored because we already called ValidateHTTPRoutes earlier - routeIndexesToPatch, _ := getRouteIndexesToPatch(r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) + httpRouteIndexesToPatch, _ := getHttpRouteIndexesToPatch(r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) + tlsRouteIndexesToPatch, _ := getTlsRouteIndexesToPatch(r.rollout.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.TLSRoutes, tlsRoutes) patches := virtualServicePatches{} - for _, routeIdx := range routeIndexesToPatch { - route := httpRoutes[routeIdx] - for j := range route.Route { - destination := route.Route[j] - - var host string - if idx := strings.Index(destination.Destination.Host, "."); idx > 0 { - host = destination.Destination.Host[:idx] - } else if idx < 0 { - host = destination.Destination.Host - } + svcSubsets := svcSubsets{ + canarySvc: canarySvc, + stableSvc: stableSvc, + canarySubset: canarySubset, + stableSubset: stableSubset, + } + // Process HTTP Routes + for _, routeIdx := range httpRouteIndexesToPatch { + if len(httpRoutes) <= routeIdx { + break + } + patches = processRoutes(Http, routeIdx, httpRoutes[routeIdx].Route, desiredWeight, svcSubsets, patches) + } + // Process TLS Routes + for _, routeIdx := range tlsRouteIndexesToPatch { + if len(tlsRoutes) <= routeIdx { + break + } + patches = processRoutes(Tls, routeIdx, tlsRoutes[routeIdx].Route, desiredWeight, svcSubsets, patches) + } + return patches +} - subset := destination.Destination.Subset - weight := destination.Weight - if (host != "" && host == canarySvc) || (subset != "" && subset == canarySubset) { - if weight != desiredWeight { - patch := virtualServicePatch{ - routeIndex: routeIdx, - destinationIndex: j, - weight: desiredWeight, - } - patches = append(patches, patch) - } - } - if (host != "" && host == stableSvc) || (subset != "" && subset == stableSubset) { - if weight != 100-desiredWeight { - patch := virtualServicePatch{ - routeIndex: routeIdx, - destinationIndex: j, - weight: 100 - desiredWeight, - } - patches = append(patches, patch) - } - } +func processRoutes(routeType string, routeIdx int, destinations []VirtualServiceRouteDestination, desiredWeight int64, svcSubsets svcSubsets, patches virtualServicePatches) virtualServicePatches { + for idx, destination := range destinations { + host := getHost(destination) + subset := destination.Destination.Subset + weight := destination.Weight + if (host != "" && host == svcSubsets.canarySvc) || (subset != "" && subset == svcSubsets.canarySubset) { + patches = appendPatch(routeIdx, routeType, weight, desiredWeight, idx, patches) + } + if (host != "" && host == svcSubsets.stableSvc) || (subset != "" && subset == svcSubsets.stableSubset) { + patches = appendPatch(routeIdx, routeType, weight, 100-desiredWeight, idx, patches) } } return patches } +func getHost(destination VirtualServiceRouteDestination) string { + var host string + if idx := strings.Index(destination.Destination.Host, "."); idx > 0 { + host = destination.Destination.Host[:idx] + } else if idx < 0 { + host = destination.Destination.Host + } + return host +} + +func appendPatch(routeIdx int, routeType string, weight int64, desiredWeight int64, destinationIndex int, patches virtualServicePatches) virtualServicePatches { + if weight != desiredWeight { + patch := virtualServicePatch{ + routeIndex: routeIdx, + routeType: routeType, + destinationIndex: destinationIndex, + weight: desiredWeight, + } + patches = append(patches, patch) + } + return patches +} + func (r *Reconciler) reconcileVirtualService(obj *unstructured.Unstructured, desiredWeight int32) (*unstructured.Unstructured, bool, error) { newObj := obj.DeepCopy() + + // HTTP Routes + var httpRoutes []VirtualServiceHTTPRoute httpRoutesI, err := GetHttpRoutesI(newObj) - if err != nil { - return nil, false, err - } - httpRoutes, err := GetHttpRoutes(newObj, httpRoutesI) - if err != nil { - return nil, false, err + if err == nil { + routes, err := GetHttpRoutes(newObj, httpRoutesI) + httpRoutes = routes + if err != nil { + return nil, false, err + } + if err := ValidateHTTPRoutes(r.rollout, httpRoutes); err != nil { + return nil, false, err + } } - if err := ValidateHTTPRoutes(r.rollout, httpRoutes); err != nil { - return nil, false, err + // TLS Routes + var tlsRoutes []VirtualServiceTLSRoute + tlsRoutesI, err := GetTlsRoutesI(newObj) + if err == nil { + routes, err := GetTlsRoutes(newObj, tlsRoutesI) + tlsRoutes = routes + if err != nil { + return nil, false, err + } + if err := ValidateTlsRoutes(r.rollout, tlsRoutes); err != nil { + return nil, false, err + } } - patches := r.generateVirtualServicePatches(httpRoutes, int64(desiredWeight)) - err = patches.patchVirtualService(httpRoutesI) + patches := r.generateVirtualServicePatches(httpRoutes, tlsRoutes, int64(desiredWeight)) + err = patches.patchVirtualService(httpRoutesI, tlsRoutesI) if err != nil { return nil, false, err } - err = unstructured.SetNestedSlice(newObj.Object, httpRoutesI, "spec", "http") + err = unstructured.SetNestedSlice(newObj.Object, httpRoutesI, "spec", Http) + if err != nil { + return newObj, len(patches) > 0, err + } + err = unstructured.SetNestedSlice(newObj.Object, tlsRoutesI, "spec", Tls) return newObj, len(patches) > 0, err } @@ -401,7 +465,7 @@ func jsonBytesToDestinationRule(dRuleBytes []byte) (*DestinationRule, error) { } func GetHttpRoutesI(obj *unstructured.Unstructured) ([]interface{}, error) { - httpRoutesI, notFound, err := unstructured.NestedSlice(obj.Object, "spec", "http") + httpRoutesI, notFound, err := unstructured.NestedSlice(obj.Object, "spec", Http) if !notFound { return nil, fmt.Errorf(".spec.http is not defined") } @@ -411,6 +475,17 @@ func GetHttpRoutesI(obj *unstructured.Unstructured) ([]interface{}, error) { return httpRoutesI, nil } +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") + } + if err != nil { + return nil, err + } + return tlsRoutesI, nil +} + func GetHttpRoutes(obj *unstructured.Unstructured, httpRoutesI []interface{}) ([]VirtualServiceHTTPRoute, error) { routeBytes, err := json.Marshal(httpRoutesI) if err != nil { @@ -426,6 +501,21 @@ func GetHttpRoutes(obj *unstructured.Unstructured, httpRoutesI []interface{}) ([ return httpRoutes, nil } +func GetTlsRoutes(obj *unstructured.Unstructured, tlsRoutesI []interface{}) ([]VirtualServiceTLSRoute, error) { + routeBytes, err := json.Marshal(tlsRoutesI) + if err != nil { + return nil, err + } + + var tlsRoutes []VirtualServiceTLSRoute + err = json.Unmarshal(routeBytes, &tlsRoutes) + if err != nil { + return nil, err + } + + return tlsRoutes, nil +} + // Type indicates this reconciler is an Istio reconciler func (r *Reconciler) Type() string { return Type @@ -472,68 +562,148 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { return true, nil } -// getRouteIndexesToPatch returns array indices of the httpRoutes which need to be patched when updating weights -func getRouteIndexesToPatch(routeNames []string, httpRoutes []VirtualServiceHTTPRoute) ([]int, error) { - var routeIndexesToPatch []int +// getHttpRouteIndexesToPatch returns array indices of the httpRoutes which need to be patched when updating weights +func getHttpRouteIndexesToPatch(routeNames []string, httpRoutes []VirtualServiceHTTPRoute) ([]int, error) { if len(routeNames) == 0 { - if len(httpRoutes) != 1 { - return nil, fmt.Errorf("VirtualService spec.http[] must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes") + return []int{0}, nil + } + + var routeIndexesToPatch []int + for _, routeName := range routeNames { + routeIndex := searchHttpRoute(routeName, httpRoutes) + if routeIndex > -1 { + routeIndexesToPatch = append(routeIndexesToPatch, routeIndex) + } else { + return nil, fmt.Errorf("HTTP Route '%s' is not found in the defined Virtual Service.", routeName) } - routeIndexesToPatch = append(routeIndexesToPatch, 0) - } else { - for _, routeName := range routeNames { - foundRoute := false - for i, route := range httpRoutes { - if route.Name == routeName { - routeIndexesToPatch = append(routeIndexesToPatch, i) - foundRoute = true - break - } - } - if !foundRoute { - return nil, fmt.Errorf("Route '%s' is not found", routeName) + } + return routeIndexesToPatch, nil +} + +func searchHttpRoute(routeName string, httpRoutes []VirtualServiceHTTPRoute) int { + routeIndex := -1 + for i, route := range httpRoutes { + if route.Name == routeName { + routeIndex = i + break + } + } + return routeIndex +} + +// getTlsRouteIndexesToPatch returns array indices of the tlsRoutes which need to be patched when updating weights +func getTlsRouteIndexesToPatch(tlsRoutes []v1alpha1.TLSRoute, istioTlsRoutes []VirtualServiceTLSRoute) ([]int, error) { + if len(tlsRoutes) == 0 { + return []int{0}, nil + } + + var routeIndexesToPatch []int + for _, tlsRoute := range tlsRoutes { + routeIndices := searchTlsRoute(tlsRoute, istioTlsRoutes) + if len(routeIndices) > 0 { + for _, routeIndex := range routeIndices { + routeIndexesToPatch = append(routeIndexesToPatch, routeIndex) } + } else { + return nil, fmt.Errorf("No matching TLS routes found in the defined Virtual Service.") } } return routeIndexesToPatch, nil } +func searchTlsRoute(tlsRoute v1alpha1.TLSRoute, istioTlsRoutes []VirtualServiceTLSRoute) []int { + routeIndices := []int{} + for i, route := range istioTlsRoutes { + portsMap := make(map[int64]bool) + sniHostsMap := make(map[string]bool) + for _, routeMatch := range route.Match { + portsMap[routeMatch.Port] = true + for _, sniHost := range routeMatch.SNI { + sniHostsMap[sniHost] = true + } + } + // If there are multiple ports defined then this rules is never gonna match. + if len(portsMap) > 1 { + continue + } + // Extract the first port number from the `portsMap` if it has more than + // zero ports in it. + var port int64 = 0 + for portNumber := range portsMap { + port = portNumber + } + sniHosts := []string{} + for sniHostName := range sniHostsMap { + sniHosts = append(sniHosts, sniHostName) + } + // To find a match for TLS Routes in Istio VS, we'll have to verify that: + // 1. There is exactly one port present in the `ports`; + // 2. The single port in `ports` matches with the `tlsRoute.Port`; + // 3. All the SNI hosts from a single match block in the VirtualService, + // matches exactly with what the user have defined in `tlsRoute.SNIHosts` + if port == tlsRoute.Port && evalUtils.Equal(tlsRoute.SNIHosts, sniHosts) { + routeIndices = append(routeIndices, i) + } + } + return routeIndices +} + // validateHTTPRoutes ensures that all the routes in the rollout exist and they only have two destinations func ValidateHTTPRoutes(r *v1alpha1.Rollout, httpRoutes []VirtualServiceHTTPRoute) error { stableSvc := r.Spec.Strategy.Canary.StableService canarySvc := r.Spec.Strategy.Canary.CanaryService - routeIndexesToPatch, err := getRouteIndexesToPatch(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) + routeIndexesToPatch, err := getHttpRouteIndexesToPatch(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes, httpRoutes) if err != nil { return err } for _, routeIndex := range routeIndexesToPatch { route := httpRoutes[routeIndex] - err := validateVirtualServiceHTTPRouteDestinations(route, stableSvc, canarySvc, r.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule) + err := validateVirtualServiceRouteDestinations(route.Route, stableSvc, canarySvc, r.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule) + if err != nil { + return err + } + } + if len(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Routes) == 0 && len(httpRoutes) > 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 +} + +// ValidateTlsRoutes ensures that all the routes in the rollout exist and they only have two destinations +func ValidateTlsRoutes(r *v1alpha1.Rollout, tlsRoutes []VirtualServiceTLSRoute) error { + stableSvc := r.Spec.Strategy.Canary.StableService + canarySvc := r.Spec.Strategy.Canary.CanaryService + + routeIndexesToPatch, err := getTlsRouteIndexesToPatch(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.TLSRoutes, tlsRoutes) + if err != nil { + return err + } + for _, routeIndex := range routeIndexesToPatch { + route := tlsRoutes[routeIndex] + err := validateVirtualServiceRouteDestinations(route.Route, stableSvc, canarySvc, r.Spec.Strategy.Canary.TrafficRouting.Istio.DestinationRule) if err != nil { return err } } + if len(r.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.TLSRoutes) == 0 && len(tlsRoutes) > 1 { + return fmt.Errorf("spec.tls[] should be set in VirtualService and it must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes") + } return nil } -// validateVirtualServiceHTTPRouteDestinations ensures there are two destinations within a route and +// validateVirtualServiceRouteDestinations ensures there are two destinations within a route and // verifies that there is both a canary and a stable host or subset specified -func validateVirtualServiceHTTPRouteDestinations(hr VirtualServiceHTTPRoute, stableSvc, canarySvc string, dRule *v1alpha1.IstioDestinationRule) error { - if len(hr.Route) != 2 { - return fmt.Errorf("Route '%s' does not have exactly two routes", hr.Name) +func validateVirtualServiceRouteDestinations(hr []VirtualServiceRouteDestination, stableSvc, canarySvc string, dRule *v1alpha1.IstioDestinationRule) error { + if len(hr) != 2 { + return fmt.Errorf("Route does not have exactly two route destinations.") } hasStableSvc := false hasCanarySvc := false hasStableSubset := false hasCanarySubset := false - for _, r := range hr.Route { - host := "" - if idx := strings.Index(r.Destination.Host, "."); idx > 0 { - host = r.Destination.Host[:idx] - } else if idx < 0 { - host = r.Destination.Host - } + for _, r := range hr { + host := getHost(r) if stableSvc != "" && host == stableSvc { hasStableSvc = true @@ -551,6 +721,10 @@ func validateVirtualServiceHTTPRouteDestinations(hr VirtualServiceHTTPRoute, sta } } } + return validateDestinationRule(dRule, hasCanarySubset, hasStableSubset, hasCanarySvc, hasStableSvc, canarySvc, stableSvc) +} + +func validateDestinationRule(dRule *v1alpha1.IstioDestinationRule, hasCanarySubset, hasStableSubset, hasCanarySvc, hasStableSvc bool, canarySvc, stableSvc string) error { if dRule != nil { if !hasCanarySubset { return fmt.Errorf("Canary DestinationRule subset '%s' not found in route", dRule.CanarySubsetName) @@ -567,5 +741,4 @@ func validateVirtualServiceHTTPRouteDestinations(hr VirtualServiceHTTPRoute, sta } } return nil - } diff --git a/rollout/trafficrouting/istio/istio_test.go b/rollout/trafficrouting/istio/istio_test.go index 9c416ad465..96d1098640 100644 --- a/rollout/trafficrouting/istio/istio_test.go +++ b/rollout/trafficrouting/istio/istio_test.go @@ -9,18 +9,23 @@ import ( "github.com/stretchr/testify/assert" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic/dynamicinformer" "k8s.io/client-go/dynamic/dynamiclister" + dynamicfake "k8s.io/client-go/dynamic/fake" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" testutil "github.com/argoproj/argo-rollouts/test/util" + evalUtils "github.com/argoproj/argo-rollouts/utils/evaluate" istioutil "github.com/argoproj/argo-rollouts/utils/istio" "github.com/argoproj/argo-rollouts/utils/record" unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" ) +const RouteMissingBothDestinationsError = "Route does not have exactly two route destinations." +const NoTlsRouteFoundError = "No matching TLS routes found in the defined Virtual Service." + func getIstioListers(client dynamic.Interface) (dynamiclister.Lister, dynamiclister.Lister) { vsvcGVR := istioutil.GetIstioVirtualServiceGVR() druleGVR := istioutil.GetIstioDestinationRuleGVR() @@ -36,7 +41,7 @@ func getIstioListers(client dynamic.Interface) (dynamiclister.Lister, dynamiclis return vsvcLister, druleLister } -func rollout(stableSvc, canarySvc, vsvc string, routes []string) *v1alpha1.Rollout { +func rollout(stableSvc, canarySvc string, istioVirtualService *v1alpha1.IstioVirtualService) *v1alpha1.Rollout { return &v1alpha1.Rollout{ ObjectMeta: metav1.ObjectMeta{ Name: "rollout", @@ -49,10 +54,7 @@ func rollout(stableSvc, canarySvc, vsvc string, routes []string) *v1alpha1.Rollo CanaryService: canarySvc, TrafficRouting: &v1alpha1.RolloutTrafficRouting{ Istio: &v1alpha1.IstioTrafficRouting{ - VirtualService: v1alpha1.IstioVirtualService{ - Name: vsvc, - Routes: routes, - }, + VirtualService: *istioVirtualService, }, }, }, @@ -61,20 +63,39 @@ func rollout(stableSvc, canarySvc, vsvc string, routes []string) *v1alpha1.Rollo } } -func checkDestination(t *testing.T, route map[string]interface{}, svc string, expectWeight int) { - destinations := route["route"].([]interface{}) - routeName := "" - if routeNameObj, ok := route["name"]; ok { - routeName = routeNameObj.(string) +func rolloutWithHttpRoutes(stableSvc, canarySvc, vsvc string, httpRoutes []string) *v1alpha1.Rollout { + istioVirtualService := &v1alpha1.IstioVirtualService{ + Name: vsvc, + Routes: httpRoutes, + } + return rollout(stableSvc, canarySvc, istioVirtualService) +} + +func rolloutWithTlsRoutes(stableSvc, canarySvc, vsvc string, tlsRoutes []v1alpha1.TLSRoute) *v1alpha1.Rollout { + istioVirtualService := &v1alpha1.IstioVirtualService{ + Name: vsvc, + TLSRoutes: tlsRoutes, } - for _, elem := range destinations { - destination := elem.(map[string]interface{}) - if destination["destination"].(map[string]interface{})["host"] == svc { - assert.Equal(t, expectWeight, int(destination["weight"].(float64))) + return rollout(stableSvc, canarySvc, istioVirtualService) +} + +func rolloutWithHttpAndTlsRoutes(stableSvc, canarySvc, vsvc string, httpRoutes []string, tlsRoutes []v1alpha1.TLSRoute) *v1alpha1.Rollout { + istioVirtualService := &v1alpha1.IstioVirtualService{ + Name: vsvc, + Routes: httpRoutes, + TLSRoutes: tlsRoutes, + } + return rollout(stableSvc, canarySvc, istioVirtualService) +} + +func checkDestination(t *testing.T, destinations []VirtualServiceRouteDestination, svc string, expectWeight int) { + for _, destination := range destinations { + if destination.Destination.Host == svc { + assert.Equal(t, expectWeight, int(destination.Weight)) return } } - msg := fmt.Sprintf("Service '%s' not found within hosts of route '%s'", svc, routeName) + msg := fmt.Sprintf("Service '%s' not found within hosts of routes.", svc) assert.Fail(t, msg) } @@ -106,6 +127,196 @@ spec: host: canary weight: 0` +const regularTlsVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - match: + - port: 3001 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +const regularMixedVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + http: + - name: primary + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - name: secondary + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - match: + - port: 3001 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +const regularMixedVsvcTwoHttpRoutes = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + http: + - name: primary + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - name: secondary + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +const regularMixedVsvcTwoTlsRoutes = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + http: + - name: primary + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - match: + - port: 3001 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +const regularTlsSniVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + tls: + - match: + - sniHosts: + - foo.bar.com + - bar.foo.com + - sniHosts: + - localhost + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + - match: + - port: 3001 + sniHosts: + - localhost + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + const singleRouteVsvc = `apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: @@ -125,31 +336,261 @@ spec: host: canary weight: 0` -func TestReconcileWeightsBaseCase(t *testing.T) { +const singleRouteTlsVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +const singleRouteMixedVsvc = `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: vsvc + namespace: default +spec: + gateways: + - istio-rollout-gateway + hosts: + - istio-rollout.dev.argoproj.io + http: + - route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0 + tls: + - match: + - port: 3000 + route: + - destination: + host: 'stable' + weight: 100 + - destination: + host: canary + weight: 0` + +func extractHttpRoutes(t *testing.T, modifiedObj *unstructured.Unstructured) []VirtualServiceHTTPRoute { + routes, ok, err := unstructured.NestedSlice(modifiedObj.Object, "spec", "http") + assert.Nil(t, err) + assert.True(t, ok) + routeBytes, _ := json.Marshal(routes) + var httpRoutes []VirtualServiceHTTPRoute + err = json.Unmarshal(routeBytes, &httpRoutes) + assert.Nil(t, err) + return httpRoutes +} + +func assertHttpRouteWeightChanges(t *testing.T, httpRoute VirtualServiceHTTPRoute, routeName string, canaryWeight, stableWeight int) { + assert.Equal(t, httpRoute.Name, routeName) + checkDestination(t, httpRoute.Route, "stable", stableWeight) + checkDestination(t, httpRoute.Route, "canary", canaryWeight) +} + +func extractTlsRoutes(t *testing.T, modifiedObj *unstructured.Unstructured) []VirtualServiceTLSRoute { + routes, ok, err := unstructured.NestedSlice(modifiedObj.Object, "spec", "tls") + assert.Nil(t, err) + assert.True(t, ok) + routeBytes, _ := json.Marshal(routes) + var tlsRoutes []VirtualServiceTLSRoute + err = json.Unmarshal(routeBytes, &tlsRoutes) + assert.Nil(t, err) + return tlsRoutes +} + +func assertTlsRouteWeightChanges(t *testing.T, tlsRoute VirtualServiceTLSRoute, snis []string, portNum, canaryWeight, stableWeight int) { + portsMap := make(map[int64]bool) + sniHostsMap := make(map[string]bool) + for _, routeMatch := range tlsRoute.Match { + if routeMatch.Port != 0 { + portsMap[routeMatch.Port] = true + } + for _, sniHost := range routeMatch.SNI { + sniHostsMap[sniHost] = true + } + } + port := 0 + for portNumber := range portsMap { + port = int(portNumber) + } + sniHosts := []string{} + for sniHostName := range sniHostsMap { + sniHosts = append(sniHosts, sniHostName) + } + if portNum != 0 { + assert.Equal(t, portNum, port) + } + if len(snis) != 0 { + assert.Equal(t, evalUtils.Equal(snis, sniHosts), true) + } + checkDestination(t, tlsRoute.Route, "stable", stableWeight) + checkDestination(t, tlsRoute.Route, "canary", canaryWeight) +} + +func TestHttpReconcileWeightsBaseCase(t *testing.T) { r := &Reconciler{ - rollout: rollout("stable", "canary", "vsvc", []string{"primary"}), + rollout: rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}), } - obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) - modifiedObj, _, err := r.reconcileVirtualService(obj, 10) + + // Test for both the HTTP VS & Mixed VS + for _, vsvc := range []string{regularVsvc, regularMixedVsvcTwoHttpRoutes} { + obj := unstructuredutil.StrToUnstructuredUnsafe(vsvc) + modifiedObj, _, err := r.reconcileVirtualService(obj, 10) + assert.Nil(t, err) + assert.NotNil(t, modifiedObj) + + // HTTP Routes + httpRoutes := extractHttpRoutes(t, modifiedObj) + + // Assertions + assertHttpRouteWeightChanges(t, httpRoutes[0], "primary", 10, 90) + assertHttpRouteWeightChanges(t, httpRoutes[1], "secondary", 0, 100) + } +} + +func TestTlsReconcileWeightsBaseCase(t *testing.T) { + r := &Reconciler{ + rollout: rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + Port: 3000, + }, + }, + ), + } + + // Test for both the TLS VS & Mixed VS + for _, vsvc := range []string{regularTlsVsvc, regularMixedVsvcTwoTlsRoutes} { + obj := unstructuredutil.StrToUnstructuredUnsafe(vsvc) + modifiedObj, _, err := r.reconcileVirtualService(obj, 30) + assert.Nil(t, err) + assert.NotNil(t, modifiedObj) + + // TLS Routes + tlsRoutes := extractTlsRoutes(t, modifiedObj) + + // Assestions + assertTlsRouteWeightChanges(t, tlsRoutes[0], nil, 3000, 30, 70) + assertTlsRouteWeightChanges(t, tlsRoutes[1], nil, 3001, 0, 100) + } +} + +func TestTlsSniReconcileWeightsBaseCase(t *testing.T) { + snis := []string{"foo.bar.com", "bar.foo.com", "localhost"} + r := &Reconciler{ + rollout: rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + SNIHosts: snis, + }, + }, + ), + } + + obj := unstructuredutil.StrToUnstructuredUnsafe(regularTlsSniVsvc) + modifiedObj, _, err := r.reconcileVirtualService(obj, 30) assert.Nil(t, err) assert.NotNil(t, modifiedObj) - routes, ok, err := unstructured.NestedSlice(modifiedObj.Object, "spec", "http") + + // TLS Routes + tlsRoutes := extractTlsRoutes(t, modifiedObj) + + // Assestions + assertTlsRouteWeightChanges(t, tlsRoutes[0], snis, 0, 30, 70) + assertTlsRouteWeightChanges(t, tlsRoutes[1], []string{"localhost"}, 3001, 0, 100) +} + +func TestTlsPortAndSniReconcileWeightsBaseCase(t *testing.T) { + snis := []string{"localhost"} + r := &Reconciler{ + rollout: rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + Port: 3001, + SNIHosts: snis, + }, + }, + ), + } + + obj := unstructuredutil.StrToUnstructuredUnsafe(regularTlsSniVsvc) + modifiedObj, _, err := r.reconcileVirtualService(obj, 30) assert.Nil(t, err) - assert.True(t, ok) - route := routes[0].(map[string]interface{}) - assert.Equal(t, route["name"].(string), "primary") - checkDestination(t, route, "stable", 90) - checkDestination(t, route, "canary", 10) - unmodifiedRoute := routes[1].(map[string]interface{}) - assert.Equal(t, unmodifiedRoute["name"].(string), "secondary") - checkDestination(t, unmodifiedRoute, "stable", 100) - checkDestination(t, unmodifiedRoute, "canary", 0) + assert.NotNil(t, modifiedObj) + + // TLS Routes + tlsRoutes := extractTlsRoutes(t, modifiedObj) + + // Assestions + assertTlsRouteWeightChanges(t, tlsRoutes[1], []string{"localhost"}, 3001, 30, 70) + assertTlsRouteWeightChanges(t, tlsRoutes[0], []string{"foo.bar.com", "bar.foo.com", "localhost"}, 0, 0, 100) +} + +func TestReconcileWeightsBaseCase(t *testing.T) { + r := &Reconciler{ + rollout: rolloutWithHttpAndTlsRoutes("stable", "canary", "vsvc", []string{"primary"}, + []v1alpha1.TLSRoute{ + { + Port: 3000, + }, + }, + ), + } + obj := unstructuredutil.StrToUnstructuredUnsafe(regularMixedVsvc) + modifiedObj, _, err := r.reconcileVirtualService(obj, 20) + assert.Nil(t, err) + assert.NotNil(t, modifiedObj) + + // HTTP Routes + httpRoutes := extractHttpRoutes(t, modifiedObj) + + // Assertions + assertHttpRouteWeightChanges(t, httpRoutes[0], "primary", 20, 80) + assertHttpRouteWeightChanges(t, httpRoutes[1], "secondary", 0, 100) + + // TLS Routes + tlsRoutes := extractTlsRoutes(t, modifiedObj) + + // Assestions + assertTlsRouteWeightChanges(t, tlsRoutes[0], nil, 3000, 20, 80) + assertTlsRouteWeightChanges(t, tlsRoutes[1], nil, 3001, 0, 100) } func TestReconcileUpdateVirtualService(t *testing.T) { - obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + AssertReconcileUpdateVirtualService(t, regularVsvc, ro) +} + +func TestTlsReconcileUpdateVirtualService(t *testing.T) { + ro := rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + Port: 3000, + }, + }, + ) + AssertReconcileUpdateVirtualService(t, regularTlsVsvc, ro) +} + +func AssertReconcileUpdateVirtualService(t *testing.T, vsvc string, ro *v1alpha1.Rollout) *dynamicfake.FakeDynamicClient { + obj := unstructuredutil.StrToUnstructuredUnsafe(vsvc) client := testutil.NewFakeDynamicClient(obj) - ro := rollout("stable", "canary", "vsvc", []string{"primary"}) vsvcLister, druleLister := getIstioListers(client) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() @@ -158,12 +599,30 @@ func TestReconcileUpdateVirtualService(t *testing.T) { actions := client.Actions() assert.Len(t, actions, 1) assert.Equal(t, "update", actions[0].GetVerb()) + return client } func TestReconcileNoChanges(t *testing.T) { obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) client := testutil.NewFakeDynamicClient(obj) - ro := rollout("stable", "canary", "vsvc", []string{"primary"}) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), nil, nil) + err := r.SetWeight(0) + assert.Nil(t, err) + assert.Len(t, client.Actions(), 1) + assert.Equal(t, "get", client.Actions()[0].GetVerb()) +} + +func TestTlsReconcileNoChanges(t *testing.T) { + obj := unstructuredutil.StrToUnstructuredUnsafe(regularTlsVsvc) + client := testutil.NewFakeDynamicClient(obj) + ro := rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + Port: 3001, + }, + }, + ) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), nil, nil) err := r.SetWeight(0) assert.Nil(t, err) @@ -174,17 +633,34 @@ func TestReconcileNoChanges(t *testing.T) { func TestReconcileInvalidValidation(t *testing.T) { obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) client := testutil.NewFakeDynamicClient(obj) - ro := rollout("stable", "canary", "vsvc", []string{"route-not-found"}) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"route-not-found"}) vsvcLister, druleLister := getIstioListers(client) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() err := r.SetWeight(0) - assert.Equal(t, "Route 'route-not-found' is not found", err.Error()) + assert.Equal(t, "HTTP Route 'route-not-found' is not found in the defined Virtual Service.", err.Error()) +} + +func TestTlsReconcileInvalidValidation(t *testing.T) { + obj := unstructuredutil.StrToUnstructuredUnsafe(regularTlsVsvc) + client := testutil.NewFakeDynamicClient(obj) + ro := rolloutWithTlsRoutes("stable", "canary", "vsvc", + []v1alpha1.TLSRoute{ + { + Port: 1001, + }, + }, + ) + vsvcLister, druleLister := getIstioListers(client) + r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) + client.ClearActions() + err := r.SetWeight(0) + assert.Equal(t, NoTlsRouteFoundError, err.Error()) } func TestReconcileVirtualServiceNotFound(t *testing.T) { client := testutil.NewFakeDynamicClient() - ro := rollout("stable", "canary", "vsvc", []string{"primary"}) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) vsvcLister, druleLister := getIstioListers(client) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() @@ -197,40 +673,92 @@ func TestReconcileVirtualServiceNotFound(t *testing.T) { func TestReconcileAmbiguousRoutes(t *testing.T) { obj := unstructuredutil.StrToUnstructuredUnsafe(regularVsvc) client := testutil.NewFakeDynamicClient(obj) - ro := rollout("stable", "canary", "vsvc", nil) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", nil) vsvcLister, druleLister := getIstioListers(client) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() err := r.SetWeight(0) - assert.Equal(t, "VirtualService spec.http[] must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes", err.Error()) + assert.Equal(t, "spec.http[] should be set in VirtualService and it must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.routes", err.Error()) } -// TestReconcileInferredSingleRoute we can support case where we infer the only route in the VirtualService -func TestReconcileInferredSingleRoute(t *testing.T) { - obj := unstructuredutil.StrToUnstructuredUnsafe(singleRouteVsvc) +func TestTlsReconcileAmbiguousRoutes(t *testing.T) { + obj := unstructuredutil.StrToUnstructuredUnsafe(regularTlsVsvc) client := testutil.NewFakeDynamicClient(obj) - ro := rollout("stable", "canary", "vsvc", nil) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", nil) vsvcLister, druleLister := getIstioListers(client) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), vsvcLister, druleLister) client.ClearActions() - err := r.SetWeight(10) + err := r.SetWeight(0) + assert.Equal(t, "spec.tls[] should be set in VirtualService and it must have exactly one route when omitting spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes", err.Error()) +} + +// TestReconcileInferredSingleRoute we can support case where we infer the only route in the VirtualService +func TestHttpReconcileInferredSingleRoute(t *testing.T) { + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", nil) + client := AssertReconcileUpdateVirtualService(t, singleRouteVsvc, ro) + + // Verify we actually made the correct change + vsvcUn, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(ro.Namespace).Get(context.TODO(), "vsvc", metav1.GetOptions{}) assert.NoError(t, err) - actions := client.Actions() - assert.Len(t, actions, 1) - assert.Equal(t, "update", actions[0].GetVerb()) + vsHttpRoutes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "http") + routeBytes, _ := json.Marshal(vsHttpRoutes) + var httpRoutes []VirtualServiceHTTPRoute + err = json.Unmarshal(routeBytes, &httpRoutes) + assert.Nil(t, err) + route := httpRoutes[0] + checkDestination(t, route.Route, "stable", 90) + checkDestination(t, route.Route, "canary", 10) +} + +func TestTlsReconcileInferredSingleRoute(t *testing.T) { + ro := rolloutWithTlsRoutes("stable", "canary", "vsvc", nil) + client := AssertReconcileUpdateVirtualService(t, singleRouteTlsVsvc, ro) // Verify we actually made the correct change vsvcUn, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(ro.Namespace).Get(context.TODO(), "vsvc", metav1.GetOptions{}) assert.NoError(t, err) - routes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "http") - route := routes[0].(map[string]interface{}) - checkDestination(t, route, "stable", 90) - checkDestination(t, route, "canary", 10) + vsTlsRoutes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "tls") + routeBytes, _ := json.Marshal(vsTlsRoutes) + var tlsRoutes []VirtualServiceTLSRoute + err = json.Unmarshal(routeBytes, &tlsRoutes) + assert.Nil(t, err) + route := tlsRoutes[0] + checkDestination(t, route.Route, "stable", 90) + checkDestination(t, route.Route, "canary", 10) +} + +func TestReconcileInferredSingleRoute(t *testing.T) { + ro := rolloutWithHttpAndTlsRoutes("stable", "canary", "vsvc", nil, nil) + client := AssertReconcileUpdateVirtualService(t, singleRouteMixedVsvc, ro) + + // Verify we actually made the correct change + vsvcUn, err := client.Resource(istioutil.GetIstioVirtualServiceGVR()).Namespace(ro.Namespace).Get(context.TODO(), "vsvc", metav1.GetOptions{}) + assert.NoError(t, err) + + // HTTP Routes + vsHttpRoutes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "http") + routeBytes, _ := json.Marshal(vsHttpRoutes) + var httpRoutes []VirtualServiceHTTPRoute + err = json.Unmarshal(routeBytes, &httpRoutes) + assert.Nil(t, err) + httpRoute := httpRoutes[0] + checkDestination(t, httpRoute.Route, "stable", 90) + checkDestination(t, httpRoute.Route, "canary", 10) + + // TLS Routes + vsTlsRoutes, _, _ := unstructured.NestedSlice(vsvcUn.Object, "spec", "tls") + routeBytes, _ = json.Marshal(vsTlsRoutes) + var tlsRoutes []VirtualServiceTLSRoute + err = json.Unmarshal(routeBytes, &tlsRoutes) + assert.Nil(t, err) + tlsRoute := tlsRoutes[0] + checkDestination(t, tlsRoute.Route, "stable", 90) + checkDestination(t, tlsRoute.Route, "canary", 10) } func TestType(t *testing.T) { client := testutil.NewFakeDynamicClient() - ro := rollout("stable", "canary", "vsvc", []string{"primary"}) + ro := rolloutWithHttpRoutes("stable", "canary", "vsvc", []string{"primary"}) r := NewReconciler(ro, client, record.NewFakeEventRecorder(), nil, nil) assert.Equal(t, Type, r.Type()) } @@ -238,13 +766,15 @@ func TestType(t *testing.T) { func TestInvalidPatches(t *testing.T) { patches := virtualServicePatches{{ routeIndex: 0, + routeType: "http", destinationIndex: 0, weight: 10, }} { invalidHTTPRoute := make([]interface{}, 1) + invalidTlsRoute := make([]interface{}, 1) invalidHTTPRoute[0] = "not a map" - err := patches.patchVirtualService(invalidHTTPRoute) + err := patches.patchVirtualService(invalidHTTPRoute, invalidTlsRoute) assert.Error(t, err, invalidCasting, "http[]", "map[string]interface") } { @@ -253,7 +783,8 @@ func TestInvalidPatches(t *testing.T) { "route": "not a []interface", }, } - err := patches.patchVirtualService(invalidHTTPRoute) + invalidTlsRoute := make([]interface{}, 1) + err := patches.patchVirtualService(invalidHTTPRoute, invalidTlsRoute) assert.Error(t, err, invalidCasting, "http[].route", "[]interface") } { @@ -264,7 +795,8 @@ func TestInvalidPatches(t *testing.T) { }, }, } - err := patches.patchVirtualService(invalidHTTPRoute) + invalidTlsRoute := make([]interface{}, 1) + err := patches.patchVirtualService(invalidHTTPRoute, invalidTlsRoute) assert.Error(t, err, invalidCasting, "http[].route[].destination", "map[string]interface") } } @@ -291,7 +823,7 @@ func TestValidateHTTPRoutes(t *testing.T) { } httpRoutes := []VirtualServiceHTTPRoute{{ Name: "test", - Route: []VirtualServiceHTTPRouteDestination{{ + Route: []VirtualServiceRouteDestination{{ Destination: VirtualServiceDestination{ Host: "stable", }, @@ -299,9 +831,9 @@ func TestValidateHTTPRoutes(t *testing.T) { }} rollout := newRollout([]string{"test"}) err := ValidateHTTPRoutes(rollout, httpRoutes) - assert.Equal(t, fmt.Errorf("Route 'test' does not have exactly two routes"), err) + assert.Equal(t, fmt.Errorf(RouteMissingBothDestinationsError), err) - httpRoutes[0].Route = []VirtualServiceHTTPRouteDestination{{ + httpRoutes[0].Route = []VirtualServiceRouteDestination{{ Destination: VirtualServiceDestination{ Host: "stable", }, @@ -315,23 +847,96 @@ func TestValidateHTTPRoutes(t *testing.T) { rolloutWithNotFoundRoute := newRollout([]string{"not-found-route"}) err = ValidateHTTPRoutes(rolloutWithNotFoundRoute, httpRoutes) - assert.Equal(t, "Route 'not-found-route' is not found", err.Error()) + assert.Equal(t, "HTTP Route 'not-found-route' is not found in the defined Virtual Service.", err.Error()) +} +func TestValidateTLSRoutes(t *testing.T) { + newRollout := func(routes []string, tlsRoutes []v1alpha1.TLSRoute) *v1alpha1.Rollout { + return &v1alpha1.Rollout{ + Spec: v1alpha1.RolloutSpec{ + Strategy: v1alpha1.RolloutStrategy{ + Canary: &v1alpha1.CanaryStrategy{ + StableService: "stable", + CanaryService: "canary", + TrafficRouting: &v1alpha1.RolloutTrafficRouting{ + Istio: &v1alpha1.IstioTrafficRouting{ + VirtualService: v1alpha1.IstioVirtualService{ + Routes: routes, + TLSRoutes: tlsRoutes, + }, + }, + }, + }, + }, + }, + } + } + tlsRoutes := []VirtualServiceTLSRoute{{ + Match: []TLSMatchAttributes{{ + Port: 3000, + }}, + Route: []VirtualServiceRouteDestination{{ + Destination: VirtualServiceDestination{ + Host: "stable", + }, + }}, + }} + rollout := newRollout([]string{}, + []v1alpha1.TLSRoute{ + { + Port: 3001, + }, + }, + ) + err := ValidateTlsRoutes(rollout, tlsRoutes) + assert.Equal(t, fmt.Errorf(NoTlsRouteFoundError), err) + + rollout = newRollout([]string{}, + []v1alpha1.TLSRoute{ + { + Port: 3000, + }, + }, + ) + err = ValidateTlsRoutes(rollout, tlsRoutes) + assert.Equal(t, fmt.Errorf(RouteMissingBothDestinationsError), err) + + tlsRoutes[0].Route = []VirtualServiceRouteDestination{{ + Destination: VirtualServiceDestination{ + Host: "stable", + }, + }, { + Destination: VirtualServiceDestination{ + Host: "canary", + }, + }} + err = ValidateTlsRoutes(rollout, tlsRoutes) + assert.Nil(t, err) + + rolloutWithNotFoundRoute := newRollout([]string{}, + []v1alpha1.TLSRoute{ + { + Port: 2002, + }, + }, + ) + err = ValidateTlsRoutes(rolloutWithNotFoundRoute, tlsRoutes) + assert.Equal(t, NoTlsRouteFoundError, err.Error()) } func TestValidateHosts(t *testing.T) { hr := VirtualServiceHTTPRoute{ Name: "test", - Route: []VirtualServiceHTTPRouteDestination{{ + Route: []VirtualServiceRouteDestination{{ Destination: VirtualServiceDestination{ Host: "stable", }, }}, } - err := validateVirtualServiceHTTPRouteDestinations(hr, "stable", "canary", nil) - assert.Equal(t, fmt.Errorf("Route 'test' does not have exactly two routes"), err) + err := validateVirtualServiceRouteDestinations(hr.Route, "stable", "canary", nil) + assert.Equal(t, fmt.Errorf(RouteMissingBothDestinationsError), err) - hr.Route = []VirtualServiceHTTPRouteDestination{{ + hr.Route = []VirtualServiceRouteDestination{{ Destination: VirtualServiceDestination{ Host: "stable", }, @@ -340,16 +945,16 @@ func TestValidateHosts(t *testing.T) { Host: "canary", }, }} - err = validateVirtualServiceHTTPRouteDestinations(hr, "stable", "canary", nil) + err = validateVirtualServiceRouteDestinations(hr.Route, "stable", "canary", nil) assert.Nil(t, err) - err = validateVirtualServiceHTTPRouteDestinations(hr, "not-found-stable", "canary", nil) + err = validateVirtualServiceRouteDestinations(hr.Route, "not-found-stable", "canary", nil) assert.Equal(t, fmt.Errorf("Stable Service 'not-found-stable' not found in route"), err) - err = validateVirtualServiceHTTPRouteDestinations(hr, "stable", "not-found-canary", nil) + err = validateVirtualServiceRouteDestinations(hr.Route, "stable", "not-found-canary", nil) assert.Equal(t, fmt.Errorf("Canary Service 'not-found-canary' not found in route"), err) - hr.Route = []VirtualServiceHTTPRouteDestination{{ + hr.Route = []VirtualServiceRouteDestination{{ Destination: VirtualServiceDestination{ Host: "stable.namespace", }, @@ -358,7 +963,7 @@ func TestValidateHosts(t *testing.T) { Host: "canary.namespace", }, }} - err = validateVirtualServiceHTTPRouteDestinations(hr, "stable", "canary", nil) + err = validateVirtualServiceRouteDestinations(hr.Route, "stable", "canary", nil) assert.Nil(t, err) } @@ -385,7 +990,7 @@ func TestValidateHTTPRoutesSubsets(t *testing.T) { } httpRoutes := []VirtualServiceHTTPRoute{{ Name: "primary", - Route: []VirtualServiceHTTPRouteDestination{ + Route: []VirtualServiceRouteDestination{ { Destination: VirtualServiceDestination{ Host: "rollout", diff --git a/rollout/trafficrouting/istio/istio_types.go b/rollout/trafficrouting/istio/istio_types.go index fb8de9c78d..63d361f42e 100644 --- a/rollout/trafficrouting/istio/istio_types.go +++ b/rollout/trafficrouting/istio/istio_types.go @@ -12,16 +12,34 @@ type VirtualService struct { type VirtualServiceSpec struct { HTTP []VirtualServiceHTTPRoute `json:"http,omitempty"` + TLS []VirtualServiceTLSRoute `json:"tls,omitempty"` } -// VirtualServiceHTTPRoute is a route in a VirtualService +// VirtualServiceHTTPRoute is a HTTP route in a VirtualService type VirtualServiceHTTPRoute struct { - Name string `json:"name,omitempty"` - Route []VirtualServiceHTTPRouteDestination `json:"route,omitempty"` + Name string `json:"name,omitempty"` + Route []VirtualServiceRouteDestination `json:"route,omitempty"` } -// VirtualServiceHTTPRouteDestination is a destination within an VirtualServiceHTTPRoute -type VirtualServiceHTTPRouteDestination struct { +// VirtualServiceTLSRoute is a TLS route in a VirtualService +type VirtualServiceTLSRoute struct { + Match []TLSMatchAttributes `json:"match,omitempty"` + Route []VirtualServiceRouteDestination `json:"route,omitempty"` +} + +// TLSMatchAttributes is the route matcher for a TLS route in a VirtualService +type TLSMatchAttributes struct { + SNI []string `json:"sniHosts,omitempty"` + DestinationSubnets []string `json:"destinationSubnets,omitempty"` + Port int64 `json:"port,omitempty"` + SourceLabels map[string]string `json:"sourceLabels,omitempty"` + Gateways []string `json:"gateways,omitempty"` + SourceNamespace string `json:"sourceNamespace,omitempty"` +} + +// VirtualServiceRouteDestination is a destination within +// { VirtualServiceHTTPRoute, VirtualServiceTLSRoute } +type VirtualServiceRouteDestination struct { // Destination holds the destination struct of the virtual service Destination VirtualServiceDestination `json:"destination,omitempty"` // Weight holds the destination struct of the virtual service diff --git a/utils/evaluate/evaluate.go b/utils/evaluate/evaluate.go index e8bf2a3645..2fd58a2842 100644 --- a/utils/evaluate/evaluate.go +++ b/utils/evaluate/evaluate.go @@ -168,3 +168,20 @@ func asFloat(in interface{}) float64 { } panic(fmt.Sprintf("asFloat() not supported on %v %v", reflect.TypeOf(in), in)) } + +// Check whether two slices of type string are equal or not. +func Equal(a, b []string) bool { + if len(a) != len(b) { + return false + } + left := make(map[string]bool) + for _, x := range a { + left[x] = true + } + for _, x := range b { + if !left[x] { + return false + } + } + return true +} diff --git a/utils/evaluate/evaluate_test.go b/utils/evaluate/evaluate_test.go index 6e94b184dc..29aeecc338 100644 --- a/utils/evaluate/evaluate_test.go +++ b/utils/evaluate/evaluate_test.go @@ -256,3 +256,9 @@ func TestIsInf(t *testing.T) { assert.True(t, isInf(inf)) assert.False(t, isInf(notInf)) } + +func TestEqual(t *testing.T) { + assert.True(t, Equal([]string{"a", "b"}, []string{"b", "a"})) + assert.False(t, Equal([]string{"a"}, []string{"a", "b"})) + assert.False(t, Equal([]string{"a", "b"}, []string{})) +} From 53b199682450e0b1812c6553b0b0a6d75f83084e Mon Sep 17 00:00:00 2001 From: cskh Date: Fri, 13 Aug 2021 16:04:43 -0400 Subject: [PATCH 10/34] fix: nil pointer in create analysisrun cmd (#1399) Signed-off-by: Hui Kang --- pkg/kubectl-argo-rollouts/cmd/create/create.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/kubectl-argo-rollouts/cmd/create/create.go b/pkg/kubectl-argo-rollouts/cmd/create/create.go index 1b33e593f7..5e1055cc76 100644 --- a/pkg/kubectl-argo-rollouts/cmd/create/create.go +++ b/pkg/kubectl-argo-rollouts/cmd/create/create.go @@ -244,6 +244,7 @@ func NewCmdCreateAnalysisRun(o *options.ArgoRolloutsOptions) *cobra.Command { SilenceUsage: true, RunE: func(c *cobra.Command, args []string) error { ctx := c.Context() + createOptions.DynamicClientset() froms := 0 if createOptions.From != "" { froms++ From de4b7e3ead82e05eaef0ed9fd48ba0474f920104 Mon Sep 17 00:00:00 2001 From: cskh Date: Tue, 17 Aug 2021 03:39:17 -0400 Subject: [PATCH 11/34] fix: remove unused ServiceNotFound condition (#1423) Signed-off-by: Hui Kang --- controller/metrics/rollout_test.go | 95 +++++++++++++++++++----------- controller/metrics/rollouts.go | 2 +- rollout/controller_test.go | 5 -- utils/conditions/conditions.go | 4 -- 4 files changed, 60 insertions(+), 46 deletions(-) diff --git a/controller/metrics/rollout_test.go b/controller/metrics/rollout_test.go index befab2a021..29a04d88ac 100644 --- a/controller/metrics/rollout_test.go +++ b/controller/metrics/rollout_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/conditions" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -47,19 +48,6 @@ status: replicas: 1 availableReplicas: 1 ` - expectedResponse = ` -# HELP rollout_info Information about rollout. -# TYPE rollout_info gauge -rollout_info{name="guestbook-bluegreen",namespace="default",phase="Progressing",strategy="blueGreen",traffic_router=""} 1 -# HELP rollout_info_replicas_available The number of available replicas per rollout. -# TYPE rollout_info_replicas_available gauge -rollout_info_replicas_available{name="guestbook-bluegreen",namespace="default"} 1 -# HELP rollout_info_replicas_desired The number of desired replicas per rollout. -# TYPE rollout_info_replicas_desired gauge -rollout_info_replicas_desired{name="guestbook-bluegreen",namespace="default"} 1 -# HELP rollout_info_replicas_unavailable The number of unavailable replicas per rollout. -# TYPE rollout_info_replicas_unavailable gauge -rollout_info_replicas_unavailable{name="guestbook-bluegreen",namespace="default"} 0` fakeCanaryRollout = ` apiVersion: argoproj.io/v1alpha1 @@ -92,51 +80,86 @@ status: replicas: 1 availableReplicas: 1 ` - - expectedCanaryResponse = ` -# HELP rollout_info Information about rollout. -# TYPE rollout_info gauge -rollout_info{name="guestbook-canary",namespace="default",phase="Progressing",strategy="canary",traffic_router="SMI"} 1 -# HELP rollout_info_replicas_available The number of available replicas per rollout. -# TYPE rollout_info_replicas_available gauge -rollout_info_replicas_available{name="guestbook-canary",namespace="default"} 1 -# HELP rollout_info_replicas_desired The number of desired replicas per rollout. -# TYPE rollout_info_replicas_desired gauge -rollout_info_replicas_desired{name="guestbook-canary",namespace="default"} 1 -# HELP rollout_info_replicas_unavailable The number of unavailable replicas per rollout. -# TYPE rollout_info_replicas_unavailable gauge -rollout_info_replicas_unavailable{name="guestbook-canary",namespace="default"} 0` ) -func newFakeRollout(fakeRollout string) *v1alpha1.Rollout { +func newFakeRollout(fakeRollout string, cond *v1alpha1.RolloutCondition) *v1alpha1.Rollout { var rollout v1alpha1.Rollout err := yaml.Unmarshal([]byte(fakeRollout), &rollout) if err != nil { panic(err) } + rollout.Status.Conditions = append(rollout.Status.Conditions, *cond) return &rollout } func TestCollectRollouts(t *testing.T) { - combinations := []testCombination{ + combinations := []struct { + fakeRollout string + fakeCondition *v1alpha1.RolloutCondition + expectedResponse string + }{ { - resource: fakeRollout, - expectedResponse: expectedResponse, + fakeRollout, + conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, "Progressing", ""), + ` +# HELP rollout_info Information about rollout. +# TYPE rollout_info gauge +rollout_info{name="guestbook-bluegreen",namespace="default",phase="Progressing",strategy="blueGreen",traffic_router=""} 1 +# HELP rollout_info_replicas_available The number of available replicas per rollout. +# TYPE rollout_info_replicas_available gauge +rollout_info_replicas_available{name="guestbook-bluegreen",namespace="default"} 1 +# HELP rollout_info_replicas_desired The number of desired replicas per rollout. +# TYPE rollout_info_replicas_desired gauge +rollout_info_replicas_desired{name="guestbook-bluegreen",namespace="default"} 1 +# HELP rollout_info_replicas_unavailable The number of unavailable replicas per rollout. +# TYPE rollout_info_replicas_unavailable gauge +rollout_info_replicas_unavailable{name="guestbook-bluegreen",namespace="default"} 0`, }, + { - resource: fakeCanaryRollout, - expectedResponse: expectedCanaryResponse, + fakeRollout, + conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, conditions.FailedRSCreateReason, "test"), + ` +# HELP rollout_info Information about rollout. +# TYPE rollout_info gauge +rollout_info{name="guestbook-bluegreen",namespace="default",phase="Error",strategy="blueGreen",traffic_router=""} 1 +# HELP rollout_info_replicas_available The number of available replicas per rollout. +# TYPE rollout_info_replicas_available gauge +rollout_info_replicas_available{name="guestbook-bluegreen",namespace="default"} 1 +# HELP rollout_info_replicas_desired The number of desired replicas per rollout. +# TYPE rollout_info_replicas_desired gauge +rollout_info_replicas_desired{name="guestbook-bluegreen",namespace="default"} 1 +# HELP rollout_info_replicas_unavailable The number of unavailable replicas per rollout. +# TYPE rollout_info_replicas_unavailable gauge +rollout_info_replicas_unavailable{name="guestbook-bluegreen",namespace="default"} 0`, + }, + { + fakeCanaryRollout, + conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, "Progressing", "test"), + ` +# HELP rollout_info Information about rollout. +# TYPE rollout_info gauge +rollout_info{name="guestbook-canary",namespace="default",phase="Progressing",strategy="canary",traffic_router="SMI"} 1 +# HELP rollout_info_replicas_available The number of available replicas per rollout. +# TYPE rollout_info_replicas_available gauge +rollout_info_replicas_available{name="guestbook-canary",namespace="default"} 1 +# HELP rollout_info_replicas_desired The number of desired replicas per rollout. +# TYPE rollout_info_replicas_desired gauge +rollout_info_replicas_desired{name="guestbook-canary",namespace="default"} 1 +# HELP rollout_info_replicas_unavailable The number of unavailable replicas per rollout. +# TYPE rollout_info_replicas_unavailable gauge +rollout_info_replicas_unavailable{name="guestbook-canary",namespace="default"} 0`, }, } for _, combination := range combinations { - testRolloutDescribe(t, combination.resource, combination.expectedResponse) + testRolloutDescribe(t, combination.fakeRollout, combination.fakeCondition, combination.expectedResponse) } } -func testRolloutDescribe(t *testing.T, fakeRollout string, expectedResponse string) { +func testRolloutDescribe(t *testing.T, fakeRollout string, cond *v1alpha1.RolloutCondition, expectedResponse string) { registry := prometheus.NewRegistry() - config := newFakeServerConfig(newFakeRollout(fakeRollout)) + config := newFakeServerConfig(newFakeRollout(fakeRollout, cond)) registry.MustRegister(NewRolloutCollector(config.RolloutLister)) mux := http.NewServeMux() mux.Handle(MetricsPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) diff --git a/controller/metrics/rollouts.go b/controller/metrics/rollouts.go index 84507c7ead..1d2de7d5b9 100644 --- a/controller/metrics/rollouts.go +++ b/controller/metrics/rollouts.go @@ -71,7 +71,7 @@ func calculatePhase(rollout *v1alpha1.Rollout) RolloutPhase { if progressing.Reason == conditions.RolloutPausedReason { phase = RolloutPaused } - if progressing.Reason == conditions.ServiceNotFoundReason || progressing.Reason == conditions.FailedRSCreateReason { + if progressing.Reason == conditions.FailedRSCreateReason { phase = RolloutError } if progressing.Reason == conditions.TimedOutReason { diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 1df5df0522..d582d4fa38 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -254,11 +254,6 @@ func newProgressingCondition(reason string, resourceObj runtime.Object, optional msg = conditions.RolloutRetryMessage status = corev1.ConditionUnknown } - case *corev1.Service: - if reason == conditions.ServiceNotFoundReason { - msg = fmt.Sprintf(conditions.ServiceNotFoundMessage, resource.Name) - status = corev1.ConditionFalse - } } if reason == conditions.RolloutPausedReason { diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index 5826db3f89..61ae1e016e 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -131,10 +131,6 @@ const ( // ReplicaSetCompletedMessage is added when the rollout is completed ReplicaSetCompletedMessage = "ReplicaSet %q has successfully progressed." - // ServiceNotFoundReason is added in a rollout when the service defined in the spec is not found - ServiceNotFoundReason = "ServiceNotFound" - // ServiceNotFoundMessage is added in a rollout when the service defined in the spec is not found - ServiceNotFoundMessage = "Service %q is not found" // ServiceReferenceReason is added to a Rollout when there is an error with a Service reference ServiceReferenceReason = "ServiceReferenceError" // ServiceReferencingManagedService is added in a rollout when the multiple rollouts reference a Rollout From bf279c36face75f41460d2659b48e8c66cabb660 Mon Sep 17 00:00:00 2001 From: cskh Date: Tue, 17 Aug 2021 03:40:19 -0400 Subject: [PATCH 12/34] fix: missing e2e test for istio tls route in rollout (#1419) - remove localhost in example as incompabile with virtual host Signed-off-by: Hui Kang --- docs/getting-started/istio/index.md | 4 +- test/e2e/istio/istio-host-http-tls-split.yaml | 99 +++++++++++++++ test/e2e/istio_test.go | 120 +++++++++++------- 3 files changed, 176 insertions(+), 47 deletions(-) create mode 100644 test/e2e/istio/istio-host-http-tls-split.yaml diff --git a/docs/getting-started/istio/index.md b/docs/getting-started/istio/index.md index 03d965069a..57c7b3b136 100644 --- a/docs/getting-started/istio/index.md +++ b/docs/getting-started/istio/index.md @@ -78,10 +78,9 @@ spec: weight: 0 tls: - match: - - port: 3000 # Should match the port number of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes + - port: 443 # Should match the port number of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes sniHosts: # Should match all the SNI hosts of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes - reviews.bookinfo.com - - localhost route: - destination: host: rollouts-demo-stable # Should match spec.strategy.canary.stableService @@ -175,7 +174,6 @@ spec: - port: 3000 sniHosts: - reviews.bookinfo.com - - localhost route: - destination: host: rollouts-demo-stable diff --git a/test/e2e/istio/istio-host-http-tls-split.yaml b/test/e2e/istio/istio-host-http-tls-split.yaml new file mode 100644 index 0000000000..356e2ba4e9 --- /dev/null +++ b/test/e2e/istio/istio-host-http-tls-split.yaml @@ -0,0 +1,99 @@ +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 + http: + - name: primary + route: + - destination: + host: istio-host-split-stable + weight: 100 + - destination: + host: istio-host-split-canary + weight: 0 + tls: + - match: + - port: 3000 + sniHosts: + - istio-host-split + 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: + strategy: + canary: + canaryService: istio-host-split-canary + stableService: istio-host-split-stable + trafficRouting: + istio: + virtualService: + name: istio-host-split-vsvc + routes: + - primary + tlsRoutes: + - port: 3000 + sniHosts: + - istio-host-split + steps: + - setWeight: 10 + - pause: {} + selector: + matchLabels: + app: istio-host-split + template: + metadata: + labels: + app: istio-host-split + spec: + containers: + - name: istio-host-split + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m diff --git a/test/e2e/istio_test.go b/test/e2e/istio_test.go index 8d63093fed..d6bcafb1d5 100644 --- a/test/e2e/istio_test.go +++ b/test/e2e/istio_test.go @@ -29,53 +29,85 @@ func (s *IstioSuite) SetupSuite() { } func (s *IstioSuite) TestIstioHostSplit() { - s.Given(). - RolloutObjects("@istio/istio-host-split.yaml"). - When(). - ApplyManifests(). - WaitForRolloutStatus("Healthy"). - Then(). - Assert(func(t *fixtures.Then) { - vsvc := t.GetVirtualService() - assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) - assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) - desired, stable := t.GetServices() - rs1 := t.GetReplicaSetByRevision("1") - assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - }). - When(). - UpdateSpec(). - WaitForRolloutStatus("Paused"). - Then(). - Assert(func(t *fixtures.Then) { - vsvc := t.GetVirtualService() - assert.Equal(s.T(), int64(90), vsvc.Spec.HTTP[0].Route[0].Weight) - assert.Equal(s.T(), int64(10), vsvc.Spec.HTTP[0].Route[1].Weight) + tests := []struct { + filename string + hasTls bool + }{ + { + "@istio/istio-host-split.yaml", + false, + }, + { + "@istio/istio-host-http-tls-split.yaml", + true, + }, + } - desired, stable := t.GetServices() - rs1 := t.GetReplicaSetByRevision("1") - rs2 := t.GetReplicaSetByRevision("2") - assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - }). - When(). - PromoteRollout(). - WaitForRolloutStatus("Healthy"). - Sleep(1*time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules - Then(). - Assert(func(t *fixtures.Then) { - vsvc := t.GetVirtualService() - assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) - assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) + for _, tc := range tests { - desired, stable := t.GetServices() - rs2 := t.GetReplicaSetByRevision("2") - assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) - }). - ExpectRevisionPodCount("1", 1) // don't scale down old replicaset since it will be within scaleDownDelay + s.Given(). + RolloutObjects(tc.filename). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) + if tc.hasTls { + assert.Equal(s.T(), int64(100), vsvc.Spec.TLS[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.TLS[0].Route[1].Weight) + } + + desired, stable := t.GetServices() + rs1 := t.GetReplicaSetByRevision("1") + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(90), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(10), vsvc.Spec.HTTP[0].Route[1].Weight) + if tc.hasTls { + assert.Equal(s.T(), int64(90), vsvc.Spec.TLS[0].Route[0].Weight) + assert.Equal(s.T(), int64(10), vsvc.Spec.TLS[0].Route[1].Weight) + } + + desired, stable := t.GetServices() + rs1 := t.GetReplicaSetByRevision("1") + rs2 := t.GetReplicaSetByRevision("2") + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1*time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(func(t *fixtures.Then) { + vsvc := t.GetVirtualService() + assert.Equal(s.T(), int64(100), vsvc.Spec.HTTP[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.HTTP[0].Route[1].Weight) + if tc.hasTls { + assert.Equal(s.T(), int64(100), vsvc.Spec.TLS[0].Route[0].Weight) + assert.Equal(s.T(), int64(0), vsvc.Spec.TLS[0].Route[1].Weight) + } + + desired, stable := t.GetServices() + rs2 := t.GetReplicaSetByRevision("2") + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + ExpectRevisionPodCount("1", 1) // don't scale down old replicaset since it will be within scaleDownDelay + + s.TearDownSuite() + } } func (s *IstioSuite) TestIstioSubsetSplit() { From 2005eca69966f59520cb88d222fb27d32fb8e0f8 Mon Sep 17 00:00:00 2001 From: "Kostis (Codefresh)" <39800303+kostis-codefresh@users.noreply.github.com> Date: Tue, 17 Aug 2021 10:40:40 +0300 Subject: [PATCH 13/34] docs: Clarify quay releases. Fixes #1408 (#1421) Signed-off-by: Kostis Kapelonis --- docs/installation.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index 3456ab6507..63d1f53479 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -23,6 +23,9 @@ This will create a new namespace, `argo-rollouts`, where Argo Rollouts controlle kubectl create clusterrolebinding YOURNAME-cluster-admin-binding --clusterrole=cluster-admin --user=YOUREMAIL@gmail.com ``` +You can find released container images of the controller at [Quay.io](https://quay.io/repository/argoproj/argo-rollouts?tab=tags). There are also old releases +at Dockerhub, but since the introduction of rate limiting, the Argo project has moved to Quay. + ## Kubectl Plugin Installation The kubectl plugin is optional, but is convenient for managing and visualizing rollouts from the From 33cbe1a90af185812a5aa52279cc6a44f993d146 Mon Sep 17 00:00:00 2001 From: Kareena Hirani Date: Tue, 17 Aug 2021 01:12:08 -0700 Subject: [PATCH 14/34] ci: add debug step to workflow (#1374) Signed-off-by: khhirani --- .github/workflows/e2e.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index f88318800d..044630ac47 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -9,6 +9,13 @@ on: branches: - 'master' - 'release-*' + workflow_dispatch: + inputs: + debug_enabled: + description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' + required: false + default: false + jobs: test-e2e: name: Run end-to-end tests @@ -36,8 +43,12 @@ jobs: kubectl apply -f test/e2e/crds - name: Start controller run: make start-e2e 2>&1 | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" > /tmp/e2e-controller.log & + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled}} - name: Run e2e tests run: make test-e2e + if: ${{ github.event_name == 'workflow_dispatch' && !github.event.inputs.debug_enabled}} - name: Upload e2e-controller logs uses: actions/upload-artifact@v2 with: From 85ed2b9c1b0d0985c994cb168aac945556fa06c3 Mon Sep 17 00:00:00 2001 From: harikrongali <81331774+harikrongali@users.noreply.github.com> Date: Tue, 17 Aug 2021 01:18:45 -0700 Subject: [PATCH 15/34] fix: analysis runs to wait for all metrics to complete (#1407) Signed-off-by: hari rongali --- analysis/analysis.go | 8 ++++ test/e2e/analysis_test.go | 23 ++++++++++ .../analysistemplate-multiple-job.yaml | 43 +++++++++++++++++++ .../analysistemplate-sleep-job.yaml | 2 +- .../rollout-inline-multiple-analysis.yaml | 28 ++++++++++++ test/fixtures/then.go | 20 +++++++++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 test/e2e/functional/analysistemplate-multiple-job.yaml create mode 100644 test/e2e/functional/rollout-inline-multiple-analysis.yaml diff --git a/analysis/analysis.go b/analysis/analysis.go index 265406ef60..ab2d6b9741 100644 --- a/analysis/analysis.go +++ b/analysis/analysis.go @@ -396,6 +396,14 @@ func (c *Controller) assessRunStatus(run *v1alpha1.AnalysisRun) (v1alpha1.Analys } } } + } else { + // metric hasn't started running. possible cases where some of the metrics starts with delay + everythingCompleted = false + if terminating { + // we have yet to take a single measurement, but have already been instructed to stop + log.Infof("metric assessed %s: run terminated", v1alpha1.AnalysisPhaseSuccessful) + return v1alpha1.AnalysisPhaseSuccessful, worstMessage + } } } if !everythingCompleted { diff --git a/test/e2e/analysis_test.go b/test/e2e/analysis_test.go index 317977c2cb..f0a9758a1a 100644 --- a/test/e2e/analysis_test.go +++ b/test/e2e/analysis_test.go @@ -25,6 +25,7 @@ func (s *AnalysisSuite) SetupSuite() { // shared analysis templates for suite s.ApplyManifests("@functional/analysistemplate-web-background.yaml") s.ApplyManifests("@functional/analysistemplate-sleep-job.yaml") + s.ApplyManifests("@functional/analysistemplate-multiple-job.yaml") } // convenience to generate a new service with a given name @@ -84,6 +85,28 @@ func (s *AnalysisSuite) TestCanaryInlineAnalysis() { ExpectAnalysisRunCount(3) } +func (s *AnalysisSuite) TestCanaryInlineMultipleAnalysis() { + s.Given(). + RolloutObjects("@functional/rollout-inline-multiple-analysis.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectAnalysisRunCount(0). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + PromoteRollout(). + Sleep(5 * time.Second). + Then(). + ExpectAnalysisRunCount(1). + ExpectInlineAnalysisRunPhase("Running"). + When(). + WaitForInlineAnalysisRunPhase("Successful"). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectAnalysisRunCount(1) +} // TestBlueGreenAnalysis tests blue-green with pre/post analysis and then fast-tracked rollback func (s *AnalysisSuite) TestBlueGreenAnalysis() { original := ` diff --git a/test/e2e/functional/analysistemplate-multiple-job.yaml b/test/e2e/functional/analysistemplate-multiple-job.yaml new file mode 100644 index 0000000000..fabfeef6a2 --- /dev/null +++ b/test/e2e/functional/analysistemplate-multiple-job.yaml @@ -0,0 +1,43 @@ +# AnalysisTemplate which sleeps for a specified duration and exits with a specified exit-code +kind: AnalysisTemplate +apiVersion: argoproj.io/v1alpha1 +metadata: + name: multiple-job +spec: + args: + - name: duration + value: 0s + - name: exit-code + value: "0" + - name: count + value: "1" + metrics: + - name: sleep-job + initialDelay: 10s + count: 1 + provider: + job: + spec: + template: + spec: + containers: + - name: sleep-job + image: nginx:1.19-alpine + command: [sh, -c, -x] + args: ["sleep {{args.duration}} && exit {{args.exit-code}}"] + restartPolicy: Never + backoffLimit: 0 + - name: sleep-job-rep + count: 1 + provider: + job: + spec: + template: + spec: + containers: + - name: sleep-job + image: nginx:1.19-alpine + command: [sh, -c, -x] + args: ["sleep {{args.duration}} && exit {{args.exit-code}}"] + restartPolicy: Never + backoffLimit: 0 \ No newline at end of file diff --git a/test/e2e/functional/analysistemplate-sleep-job.yaml b/test/e2e/functional/analysistemplate-sleep-job.yaml index 462dda8d7a..39b242af99 100644 --- a/test/e2e/functional/analysistemplate-sleep-job.yaml +++ b/test/e2e/functional/analysistemplate-sleep-job.yaml @@ -25,4 +25,4 @@ spec: command: [sh, -c, -x] args: ["sleep {{args.duration}} && exit {{args.exit-code}}"] restartPolicy: Never - backoffLimit: 0 + backoffLimit: 0 \ No newline at end of file diff --git a/test/e2e/functional/rollout-inline-multiple-analysis.yaml b/test/e2e/functional/rollout-inline-multiple-analysis.yaml new file mode 100644 index 0000000000..521424f1c3 --- /dev/null +++ b/test/e2e/functional/rollout-inline-multiple-analysis.yaml @@ -0,0 +1,28 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-inline-analysis +spec: + strategy: + canary: + steps: + - setWeight: 10 + - pause: {} + - analysis: + templates: + - templateName: multiple-job + selector: + matchLabels: + app: rollout-inline-analysis + template: + metadata: + labels: + app: rollout-inline-analysis + spec: + containers: + - name: rollouts-demo + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 5m diff --git a/test/fixtures/then.go b/test/fixtures/then.go index 77a04b2faf..b99d65347c 100644 --- a/test/fixtures/then.go +++ b/test/fixtures/then.go @@ -257,6 +257,26 @@ func (t *Then) ExpectBackgroundAnalysisRunPhase(phase string) *Then { ) } +func (t *Then) ExpectInlineAnalysisRun(expectation string, expectFunc AnalysisRunExpectation) *Then { + t.t.Helper() + bgArun := t.GetInlineAnalysisRun() + if !expectFunc(bgArun) { + t.log.Errorf("Inline AnalysisRun expectation '%s' failed", expectation) + t.t.FailNow() + } + t.log.Infof("Inline AnalysisRun expectation '%s' met", expectation) + return t +} + +func (t *Then) ExpectInlineAnalysisRunPhase(phase string) *Then { + t.t.Helper() + return t.ExpectInlineAnalysisRun(fmt.Sprintf("inline analysis phase == %s", phase), + func(run *rov1.AnalysisRun) bool { + return string(run.Status.Phase) == phase + }, + ) +} + // ExpectStableRevision verifies the ReplicaSet with the specified revision is marked stable func (t *Then) ExpectStableRevision(revision string) *Then { t.t.Helper() From ba01a14e1e10a36da79306c8381d51e5fcc279c9 Mon Sep 17 00:00:00 2001 From: cskh Date: Tue, 17 Aug 2021 04:20:15 -0400 Subject: [PATCH 16/34] docs: clarify the setCanaryScale bahavior (#1424) Signed-off-by: Hui Kang --- docs/features/specification.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/features/specification.md b/docs/features/specification.md index d719d74ca6..abf2e5ddee 100644 --- a/docs/features/specification.md +++ b/docs/features/specification.md @@ -238,11 +238,12 @@ spec: # Pauses indefinitely until manually resumed - pause: {} - # set canary scale to a explicit count (supported only with trafficRouting) + # set canary scale to a explicit count without changing traffic weight + # (supported only with trafficRouting) - setCanaryScale: replicas: 3 - # set canary scale to a percentage of spec.replicas + # set canary scale to a percentage of spec.replicas without changing traffic weight # (supported only with trafficRouting) - setCanaryScale: weight: 25 From 0b707757f5cba1cce994cd17ee6fd9d2bce304c3 Mon Sep 17 00:00:00 2001 From: "Kostis (Codefresh)" <39800303+kostis-codefresh@users.noreply.github.com> Date: Tue, 17 Aug 2021 21:31:33 +0300 Subject: [PATCH 17/34] docs: clarify analysis. Fixes #1234 (#1400) Signed-off-by: Kostis Kapelonis --- docs/architecture.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 51eb38c8f6..b92a21164c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -36,13 +36,15 @@ This is the mechanism that traffic from live users enters your cluster and is re Argo Rollouts is very flexible on networking options. First of all you can have different services during a Rollout, that go only to the new version, only to the old version or both. Specifically for Canary deployments, Argo Rollouts supports several [service mesh and ingress solutions](../features/traffic-management/) for splitting traffic with specific percentages instead of simple balancing based on pod counts. -## Analysis and AnalysisRun +## AnalysisTemplate and AnalysisRun -Analysis is a custom Kubernetes resource that connects a Rollout to your metrics provider and defines specific thresholds for certain metrics that will decide if a rollout is successful or not. For each Analysis you can define one or more metric queries along with their expected results. A Rollout will progress on its own if metric queries are good, rollback automatically if metrics show failure and pause the rollout if metrics cannot provide a success/failure answer. +Analysis is the capability to connect a Rollout to your metrics provider and define specific thresholds for certain metrics that will decide if an update is successful or not. For each analysis you can define one or more metric queries along with their expected results. A Rollout will progress on its own if metric queries are good, rollback automatically if metrics show failure and pause the rollout if metrics cannot provide a success/failure answer. -Analysis is just a template on what metrics to query. The actual result that is attached to a Rollout is the `AnalysisRun` custom resource. You can define an Analysis on a specific Rollout or globally on the cluster to be shared by multiple rollouts. The AnalysisRun resources is scoped on a specific rollout. +For performing an analysis, Argo Rollouts includes two custom Kubernetes resources: `AnalysisTemplate` and `AnalysisRun`. -Note that using Analysis and metrics in a Rollout is completely optional. You can manually pause and promote a rollout or use other external methods (e.g. smoke tests) via the API or the CLI. You don't need a metric solution just to use Argo Rollouts. You can also mix both automated (i.e. analysis based) and manual steps in a Rollout. +`AnalysisTemplate` contains instructions on what metrics to query. The actual result that is attached to a Rollout is the `AnalysisRun` custom resource. You can define an `AnalysisTemplate` on a specific Rollout or globally on the cluster to be shared by multiple rollouts as a `ClusterAnalysisTemplate`. The `AnalysisRun` resource is scoped on a specific rollout. + +Note that using an analysis and metrics in a Rollout is completely optional. You can manually pause and promote a rollout or use other external methods (e.g. smoke tests) via the API or the CLI. You don't need a metric solution just to use Argo Rollouts. You can also mix both automated (i.e. analysis based) and manual steps in a Rollout. Apart from metrics, you can also decide the success of a rollout by running a [Kubernetes job](../analysis/job/) or running [a webhook](../analysis/web/). From a3e4feb7a2266b23b0e70521774aeebcfda988dc Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Thu, 19 Aug 2021 10:56:47 -0700 Subject: [PATCH 18/34] docs: add @huikang as a reviewer (#1432) Signed-off-by: Jesse Suen --- OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS b/OWNERS index 8c65e5b3dc..ef7133eca0 100644 --- a/OWNERS +++ b/OWNERS @@ -9,3 +9,4 @@ approvers: reviewers: - dthomson25 +- huikang \ No newline at end of file From 601503be981b22f5f335a53cc0b128509e137376 Mon Sep 17 00:00:00 2001 From: Volodymyr Date: Fri, 20 Aug 2021 06:57:55 +0300 Subject: [PATCH 19/34] docs: small typo (#1428) Signed-off-by: Volodymyr Shynkar --- docs/features/traffic-management/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/traffic-management/index.md b/docs/features/traffic-management/index.md index 6e173c0836..26b7c5a720 100644 --- a/docs/features/traffic-management/index.md +++ b/docs/features/traffic-management/index.md @@ -10,7 +10,7 @@ There are various techniques to achieve traffic management: ## Traffic Management tools in Kubernetes -The core Kubernetes objects do not have fine-grained tools needed to fulfill all the requirements of traffic management. At most, Kubernetes offers naïve load balancing capabilities through the Service object by offering an endpoint that routes traffic to a grouping of pods based on that Service's selector. Functionality like traffic mirroring or routing by headers is not possible with the default core Service object, and the only way to control the percentage of traffic to different versions of an application is by manipulating replica counts of those versions. +The core Kubernetes objects do not have fine-grained tools needed to fulfill all the requirements of traffic management. At most, Kubernetes offers native load balancing capabilities through the Service object by offering an endpoint that routes traffic to a grouping of pods based on that Service's selector. Functionality like traffic mirroring or routing by headers is not possible with the default core Service object, and the only way to control the percentage of traffic to different versions of an application is by manipulating replica counts of those versions. Service Meshes fill this missing functionality in Kubernetes. They introduce new concepts and functionality to control the data plane through the use of CRDs and other core Kubernetes resources. From be80cb8a76a3520af07924a38d80a40dca6c80fb Mon Sep 17 00:00:00 2001 From: Remington Breeze Date: Fri, 20 Aug 2021 12:37:10 -0700 Subject: [PATCH 20/34] chore: Bump argo-ui version (#1437) Signed-off-by: Remington Breeze --- ui/package.json | 7 +- ui/src/app/App.scss | 11 +- ui/src/app/App.tsx | 8 +- ui/src/app/components/header/header.scss | 2 +- ui/src/app/components/header/header.tsx | 30 +- ui/src/app/components/modal/modal.scss | 2 +- ui/src/app/components/modal/modal.tsx | 4 +- ui/src/app/components/pods/pods.scss | 2 +- ui/src/app/components/pods/pods.tsx | 25 +- .../rollout-actions/rollout-actions.tsx | 13 +- ui/src/app/components/rollout/rollout.scss | 2 +- ui/src/app/components/rollout/rollout.tsx | 63 +- .../rollouts-list/rollouts-list.scss | 2 +- .../rollouts-list/rollouts-list.tsx | 16 +- .../app/components/shortcuts/shortcuts.scss | 2 +- ui/src/app/components/shortcuts/shortcuts.tsx | 18 +- .../components/status-icon/status-icon.scss | 2 +- .../components/status-icon/status-icon.tsx | 29 +- ui/src/app/shared/utils/utils.tsx | 11 +- ui/src/app/webpack.common.js | 10 +- ui/yarn.lock | 3563 ++--------------- 21 files changed, 435 insertions(+), 3387 deletions(-) diff --git a/ui/package.json b/ui/package.json index f2a3356e66..5a27c50c0a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -3,11 +3,6 @@ "version": "0.1.0", "private": true, "dependencies": { - "@fortawesome/fontawesome-free": "^5.15.2", - "@fortawesome/fontawesome-svg-core": "^1.2.34", - "@fortawesome/free-regular-svg-icons": "^5.15.2", - "@fortawesome/free-solid-svg-icons": "^5.15.2", - "@fortawesome/react-fontawesome": "^0.1.14", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", @@ -17,7 +12,7 @@ "@types/react-dom": "^16.9.3", "@types/react-helmet": "^6.1.0", "@types/react-router-dom": "^5.1.7", - "argo-ux": "^1.1.4", + "argo-ui": "git+https://github.com/argoproj/argo-ui.git", "moment": "^2.29.1", "moment-timezone": "^0.5.33", "portable-fetch": "^3.0.0", diff --git a/ui/src/app/App.scss b/ui/src/app/App.scss index bcb38482a7..ac371917d0 100644 --- a/ui/src/app/App.scss +++ b/ui/src/app/App.scss @@ -1,4 +1,13 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; + +$argo-icon-fonts-root: 'assets/fonts/'; +$fa-font-path: 'assets/fonts'; + +@import 'node_modules/argo-ui/src/styles/argo-icon'; + +@import 'node_modules/@fortawesome/fontawesome-free/scss/fontawesome'; +@import 'node_modules/@fortawesome/fontawesome-free/scss/solid'; +@import 'node_modules/@fortawesome/fontawesome-free/scss/brands'; body, html { diff --git a/ui/src/app/App.tsx b/ui/src/app/App.tsx index 669055c2fb..bbc5bec71f 100644 --- a/ui/src/app/App.tsx +++ b/ui/src/app/App.tsx @@ -1,5 +1,4 @@ -import {faArrowDown, faArrowLeft, faArrowRight, faArrowUp} from '@fortawesome/free-solid-svg-icons'; -import {ThemeDiv} from 'argo-ux'; +import {ThemeDiv, ThemeProvider} from 'argo-ui/v2'; import {Header} from './components/header/header'; import {createBrowserHistory} from 'history'; import * as React from 'react'; @@ -11,13 +10,12 @@ import {Modal} from './components/modal/modal'; import {Rollout} from './components/rollout/rollout'; import {RolloutsList} from './components/rollouts-list/rollouts-list'; import {Shortcut, Shortcuts} from './components/shortcuts/shortcuts'; -import {ThemeProvider} from 'argo-ux'; const bases = document.getElementsByTagName('base'); const base = bases.length > 0 ? bases[0].getAttribute('href') || '/' : '/'; export const history = createBrowserHistory({basename: base}); -const Page = (props: {path: string; component: React.ReactNode; exact?: boolean; shortcuts?: Shortcut[], changeNamespace: (val: string) => void}) => { +const Page = (props: {path: string; component: React.ReactNode; exact?: boolean; shortcuts?: Shortcut[]; changeNamespace: (val: string) => void}) => { const {useKeybinding} = React.useContext(KeybindingContext); const [showShortcuts, setShowShortcuts] = React.useState(false); useKeybinding( @@ -90,7 +88,7 @@ const App = () => { shortcuts={[ {key: '/', description: 'Search'}, {key: 'TAB', description: 'Search, navigate search items'}, - {key: [faArrowLeft, faArrowRight, faArrowUp, faArrowDown], description: 'Navigate rollouts list'}, + {key: ['fa-arrow-left', 'fa-arrow-right', 'fa-arrow-up', 'fa-arrow-down'], description: 'Navigate rollouts list', icon: true}, {key: ['SHIFT', 'H'], description: 'Show help menu', combo: true}, ]} changeNamespace={changeNamespace} diff --git a/ui/src/app/components/header/header.scss b/ui/src/app/components/header/header.scss index 27c42f8c4a..395271d8f2 100644 --- a/ui/src/app/components/header/header.scss +++ b/ui/src/app/components/header/header.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; .rollouts-header { display: flex; diff --git a/ui/src/app/components/header/header.tsx b/ui/src/app/components/header/header.tsx index 4b96b67ae4..ff58a99114 100644 --- a/ui/src/app/components/header/header.tsx +++ b/ui/src/app/components/header/header.tsx @@ -1,18 +1,8 @@ import * as React from 'react'; -import { - ActionButton, - Brand, - InfoItemRow, - ThemeToggle, - Tooltip, - Header as GenericHeader, - Autocomplete, - ThemeDiv, -} from 'argo-ux'; +import {ActionButton, Brand, InfoItemRow, ThemeToggle, Tooltip, Header as GenericHeader, Autocomplete, ThemeDiv} from 'argo-ui/v2'; import {useParams} from 'react-router'; import {NamespaceContext, RolloutAPIContext} from '../../shared/context/api'; -import {faBook, faKeyboard} from '@fortawesome/free-solid-svg-icons'; import './header.scss'; import {Link, useHistory} from 'react-router-dom'; @@ -41,12 +31,12 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val:
{props.pageHasShortcuts && ( - + )} - + @@ -59,11 +49,15 @@ export const Header = (props: {pageHasShortcuts: boolean; changeNamespace: (val: ) : (
NS:
- setNsInput(el.target.value)} - onItemClick={(val) => { props.changeNamespace(val ? val : nsInput); history.push(`/rollouts`) } } - value={nsInput} + setNsInput(el.target.value)} + onItemClick={(val) => { + props.changeNamespace(val ? val : nsInput); + history.push(`/rollouts`); + }} + value={nsInput} />
)} diff --git a/ui/src/app/components/modal/modal.scss b/ui/src/app/components/modal/modal.scss index f18e976e41..477c5ea230 100644 --- a/ui/src/app/components/modal/modal.scss +++ b/ui/src/app/components/modal/modal.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; .modal-container { width: 100%; diff --git a/ui/src/app/components/modal/modal.tsx b/ui/src/app/components/modal/modal.tsx index 12c000efa0..66f3cd5c3b 100644 --- a/ui/src/app/components/modal/modal.tsx +++ b/ui/src/app/components/modal/modal.tsx @@ -1,5 +1,3 @@ -import {faTimesCircle} from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import * as React from 'react'; import {Key, KeybindingContext} from 'react-keyhooks'; @@ -19,7 +17,7 @@ export const Modal = (props: {children: React.ReactNode; hide?: () => void}) =>
- +
{props.children}
diff --git a/ui/src/app/components/pods/pods.scss b/ui/src/app/components/pods/pods.scss index 2304bc6ac8..29b72ac338 100644 --- a/ui/src/app/components/pods/pods.scss +++ b/ui/src/app/components/pods/pods.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; $POD_SIZE: 30px; diff --git a/ui/src/app/components/pods/pods.tsx b/ui/src/app/components/pods/pods.tsx index a1d87b8c68..905160c79e 100644 --- a/ui/src/app/components/pods/pods.tsx +++ b/ui/src/app/components/pods/pods.tsx @@ -1,7 +1,4 @@ -import {faQuestionCircle} from '@fortawesome/free-regular-svg-icons'; -import {faCheck, faCircleNotch, faClipboard, faExclamationTriangle, faTimes} from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {Menu, ThemeDiv, Tooltip, WaitFor} from 'argo-ux'; +import {Menu, ThemeDiv, Tooltip, WaitFor} from 'argo-ui/v2'; import * as React from 'react'; import {RolloutReplicaSetInfo} from '../../../models/rollout/generated'; import {Pod} from '../../../models/rollout/rollout'; @@ -42,41 +39,41 @@ export const PodIcon = (props: {status: string}) => { let icon; let spin = false; if (status.startsWith('Init:')) { - icon = faCircleNotch; + icon = 'fa-circle-notch'; spin = true; } if (status.startsWith('Signal:') || status.startsWith('ExitCode:')) { - icon = faTimes; + icon = 'fa-times'; } if (status.endsWith('Error') || status.startsWith('Err')) { - icon = faExclamationTriangle; + icon = 'fa-exclamation-triangle'; } const className = ParsePodStatus(status); switch (className) { case PodStatus.Pending: - icon = faCircleNotch; + icon = 'fa-circle-notch'; spin = true; break; case PodStatus.Success: - icon = faCheck; + icon = 'fa-check'; break; case PodStatus.Failed: - icon = faTimes; + icon = 'fa-times'; break; case PodStatus.Warning: - icon = faExclamationTriangle; + icon = 'fa-exclamation-triangle'; break; default: spin = false; - icon = faQuestionCircle; + icon = 'fa-question-circle'; break; } return ( - + ); }; @@ -127,7 +124,7 @@ export const ReplicaSet = (props: {rs: RolloutReplicaSetInfo; showRevision?: boo }; export const PodWidget = (props: {pod: Pod}) => ( - navigator.clipboard.writeText(props.pod.objectMeta?.name), icon: faClipboard}]}> + navigator.clipboard.writeText(props.pod.objectMeta?.name), icon: 'fa-clipboard'}]}> diff --git a/ui/src/app/components/rollout-actions/rollout-actions.tsx b/ui/src/app/components/rollout-actions/rollout-actions.tsx index 5c7ebb6f06..fb7c8321e0 100644 --- a/ui/src/app/components/rollout-actions/rollout-actions.tsx +++ b/ui/src/app/components/rollout-actions/rollout-actions.tsx @@ -1,9 +1,8 @@ -import {faArrowCircleUp, faChevronCircleUp, faExclamationCircle, faRedoAlt, faSync} from '@fortawesome/free-solid-svg-icons'; import * as React from 'react'; import {RolloutInfo} from '../../../models/rollout/rollout'; import {NamespaceContext, RolloutAPIContext} from '../../shared/context/api'; import {formatTimestamp} from '../../shared/utils/utils'; -import {ActionButton, ActionButtonProps} from 'argo-ux'; +import {ActionButton, ActionButtonProps} from 'argo-ui/v2'; import {RolloutStatus} from '../status-icon/status-icon'; export enum RolloutAction { @@ -25,7 +24,7 @@ export const RolloutActionButton = (props: {action: RolloutAction; rollout: Roll RolloutAction.Restart, { label: 'RESTART', - icon: faSync, + icon: 'fa-sync', action: api.rolloutServiceRestartRollout, tooltip: restartedAt === 'Never' ? 'Never restarted' : `Last restarted ${restartedAt}`, shouldConfirm: true, @@ -35,7 +34,7 @@ export const RolloutActionButton = (props: {action: RolloutAction; rollout: Roll RolloutAction.Retry, { label: 'RETRY', - icon: faRedoAlt, + icon: 'fa-redo-alt', action: api.rolloutServiceRetryRollout, shouldConfirm: true, }, @@ -44,7 +43,7 @@ export const RolloutActionButton = (props: {action: RolloutAction; rollout: Roll RolloutAction.Abort, { label: 'ABORT', - icon: faExclamationCircle, + icon: 'fa-exclamation-circle', action: api.rolloutServiceAbortRollout, shouldConfirm: true, }, @@ -53,7 +52,7 @@ export const RolloutActionButton = (props: {action: RolloutAction; rollout: Roll RolloutAction.Promote, { label: 'PROMOTE', - icon: faChevronCircleUp, + icon: 'fa-chevron-circle-up', action: api.rolloutServicePromoteRollout, body: {full: false}, disabled: props.rollout.status !== RolloutStatus.Paused, @@ -64,7 +63,7 @@ export const RolloutActionButton = (props: {action: RolloutAction; rollout: Roll RolloutAction.PromoteFull, { label: 'PROMOTE-FULL', - icon: faArrowCircleUp, + icon: 'fa-arrow-circle-up', body: {full: true}, action: api.rolloutServicePromoteRollout, disabled: props.rollout.status !== RolloutStatus.Paused, diff --git a/ui/src/app/components/rollout/rollout.scss b/ui/src/app/components/rollout/rollout.scss index a5b6c973e2..897f43e1ef 100644 --- a/ui/src/app/components/rollout/rollout.scss +++ b/ui/src/app/components/rollout/rollout.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; .revision { padding: 15px; diff --git a/ui/src/app/components/rollout/rollout.tsx b/ui/src/app/components/rollout/rollout.tsx index 5086d74d04..638c107236 100644 --- a/ui/src/app/components/rollout/rollout.tsx +++ b/ui/src/app/components/rollout/rollout.tsx @@ -1,25 +1,4 @@ -import {faChartBar} from '@fortawesome/free-regular-svg-icons'; -import { - faBalanceScale, - faBalanceScaleRight, - faBoxes, - faChevronCircleDown, - faChevronCircleUp, - faDove, - faExclamationCircle, - faFlask, - faPalette, - faPauseCircle, - faPencilAlt, - faSave, - faShoePrints, - faTimes, - faUndoAlt, - faWeight, - IconDefinition, -} from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {ActionButton, Autocomplete, EffectDiv, InfoItem, InfoItemKind, InfoItemProps, InfoItemRow, Spinner, ThemeDiv, Tooltip, useInput, WaitFor} from 'argo-ux'; +import {ActionButton, Autocomplete, EffectDiv, InfoItem, InfoItemKind, InfoItemProps, InfoItemRow, Spinner, ThemeDiv, Tooltip, useInput, WaitFor} from 'argo-ui/v2'; import * as React from 'react'; import {Helmet} from 'react-helmet'; import {Key, KeybindingContext} from 'react-keyhooks'; @@ -162,9 +141,9 @@ export const Rollout = () => { {rollout.strategy === Strategy.Canary && ( - - - {' '} + + + {' '} )} @@ -219,9 +198,9 @@ export const Rollout = () => { const iconForStrategy = (s: Strategy) => { switch (s) { case Strategy.Canary: - return faDove; + return 'fa-dove'; case Strategy.BlueGreen: - return faPalette; + return 'fa-palette'; } }; @@ -229,11 +208,11 @@ const ImageItems = (props: {images: ImageInfo[]}) => { return (
{props.images.map((img) => { - let imageItems = img.tags.map((t) => { + let imageItems = img?.tags?.map((t) => { return {content: t, icon: IconForTag(t)} as InfoItemProps; }); if (imageItems.length === 0) { - imageItems = null; + imageItems = []; } return {img.image}} items={imageItems} />; })} @@ -289,7 +268,7 @@ const ProcessRevisions = (ri: RolloutInfo): Revision[] => { const RevisionWidget = (props: {revision: Revision; initCollapsed?: boolean; rollback: (revision: number) => void; current: boolean}) => { const {revision, initCollapsed} = props; const [collapsed, setCollapsed] = React.useState(initCollapsed); - const icon = collapsed ? faChevronCircleDown : faChevronCircleUp; + const icon = collapsed ? 'fa-chevron-circle-down' : 'fa-chevron-circle-up'; const images = parseImages(revision.replicaSets); return ( @@ -297,10 +276,10 @@ const RevisionWidget = (props: {revision: Revision; initCollapsed?: boolean; rol Revision {revision.number}
{!props.current && ( - props.rollback(revision.number)} label='ROLLBACK' icon={faUndoAlt} style={{fontSize: '13px'}} indicateLoading shouldConfirm /> + props.rollback(revision.number)} label='ROLLBACK' icon='fa-undo-alt' style={{fontSize: '13px'}} indicateLoading shouldConfirm /> )} setCollapsed(!collapsed)}> - +
@@ -371,7 +350,7 @@ const ContainersWidget = (props: { {editing ? (
{ setEditing(false); setError(false); @@ -380,7 +359,7 @@ const ContainersWidget = (props: { { for (const container of Object.keys(inputs)) { const split = inputs[container].split(':'); @@ -401,7 +380,7 @@ const ContainersWidget = (props: { />
) : ( - setEditing(true)} style={{cursor: 'pointer', marginLeft: 'auto'}} /> + setEditing(true)} style={{cursor: 'pointer', marginLeft: 'auto'}} /> )}
{containers.map((c, i) => ( @@ -420,7 +399,7 @@ const ContainersWidget = (props: { {containers.length < 2 && ( - + Add more containers to fill this space! @@ -452,16 +431,16 @@ const parseDuration = (duration: string): string => { }; const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1CanaryStep; complete?: boolean; current?: boolean; last?: boolean}) => { - let icon: IconDefinition; + let icon: string; let content = ''; let unit = ''; if (props.step.setWeight) { - icon = faWeight; + icon = 'fa-weight'; content = `Set Weight: ${props.step.setWeight}`; unit = '%'; } if (props.step.pause) { - icon = faPauseCircle; + icon = 'fa-pause-circle'; if (props.step.pause.duration) { content = `Pause: ${parseDuration(`${props.step.pause.duration}`)}`; } else { @@ -470,20 +449,20 @@ const Step = (props: {step: GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1 } if (props.step.analysis) { content = 'Analysis'; - icon = faChartBar; + icon = 'fa-chart-bar'; } if (props.step.setCanaryScale) { content = 'Canary Scale'; } if (props.step.experiment) { content = 'Experiment'; - icon = faFlask; + icon = 'fa-flask'; } return ( - {content} + {content} {unit} {!props.last && } diff --git a/ui/src/app/components/rollouts-list/rollouts-list.scss b/ui/src/app/components/rollouts-list/rollouts-list.scss index 8e4ecf5f70..b5c58c61cd 100644 --- a/ui/src/app/components/rollouts-list/rollouts-list.scss +++ b/ui/src/app/components/rollouts-list/rollouts-list.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; $WIDGET_WIDTH: 400px; diff --git a/ui/src/app/components/rollouts-list/rollouts-list.tsx b/ui/src/app/components/rollouts-list/rollouts-list.tsx index 58c63bee6f..dfbcb1d515 100644 --- a/ui/src/app/components/rollouts-list/rollouts-list.tsx +++ b/ui/src/app/components/rollouts-list/rollouts-list.tsx @@ -1,6 +1,4 @@ -import {faCircleNotch, faDove, faPalette, faRedoAlt, faSearch, faWeight} from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {Autocomplete, EffectDiv, InfoItemKind, InfoItemRow, Spinner, ThemeDiv, useAutocomplete, WaitFor} from 'argo-ux'; +import {Autocomplete, EffectDiv, InfoItemKind, InfoItemRow, Spinner, ThemeDiv, useAutocomplete, WaitFor} from 'argo-ui/v2'; import * as React from 'react'; import {Key, KeybindingContext, useNav} from 'react-keyhooks'; import {Link, useHistory} from 'react-router-dom'; @@ -100,7 +98,7 @@ export const RolloutsList = () => { placeholder='Search...' style={{marginBottom: '1.5em'}} onItemClick={(item) => history.push(`/rollout/${item}`)} - icon={faSearch} + icon='fa-search' {...searchInput} />
@@ -191,9 +189,9 @@ export const RolloutWidget = (props: {rollout: RolloutInfo; deselect: () => void - {(rollout.strategy || '').toLocaleLowerCase() === 'canary' && } + {(rollout.strategy || '').toLocaleLowerCase() === 'canary' && } }> @@ -217,16 +215,14 @@ const WidgetHeader = (props: {rollout: RolloutInfo; refresh: () => void}) => {
{rollout.objectMeta?.name} - { props.refresh(); setLoading(true); e.preventDefault(); }} - spin={loading} /> diff --git a/ui/src/app/components/shortcuts/shortcuts.scss b/ui/src/app/components/shortcuts/shortcuts.scss index 450d875636..ca4cd0e227 100644 --- a/ui/src/app/components/shortcuts/shortcuts.scss +++ b/ui/src/app/components/shortcuts/shortcuts.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; .shortcuts { position: absolute; diff --git a/ui/src/app/components/shortcuts/shortcuts.tsx b/ui/src/app/components/shortcuts/shortcuts.tsx index cb953d92dd..b3394b1bdf 100644 --- a/ui/src/app/components/shortcuts/shortcuts.tsx +++ b/ui/src/app/components/shortcuts/shortcuts.tsx @@ -1,16 +1,12 @@ -import {IconDefinition} from '@fortawesome/fontawesome-common-types'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import * as React from 'react'; import './shortcuts.scss'; -type StringOrIcon = string | IconDefinition; -type StringsOrIcons = (string | IconDefinition)[]; - export interface Shortcut { - key?: StringOrIcon | StringsOrIcons; + key?: string | string[]; description: string; combo?: boolean; + icon?: boolean; } export const Shortcuts = (props: {shortcuts: Shortcut[]}) => { @@ -21,19 +17,19 @@ export const Shortcuts = (props: {shortcuts: Shortcut[]}) => {
{props.shortcuts.map((sc, i) => { if (!Array.isArray(sc.key)) { - sc.key = [sc.key as StringOrIcon]; + sc.key = [sc.key]; } return (
- {(sc.key as StringsOrIcons).map((k, i) => { + {(sc.key || []).map((k, i) => { let contents: any = k; - if (typeof k !== 'string') { - contents = ; + if (sc.icon) { + contents = ; } return (
{contents}
- {sc.combo && i !== (sc.key as StringsOrIcons).length - 1 &&
+
} + {sc.combo && i !== sc.key?.length - 1 &&
+
}
); })} diff --git a/ui/src/app/components/status-icon/status-icon.scss b/ui/src/app/components/status-icon/status-icon.scss index f8d916201f..4eb1a1e9e3 100644 --- a/ui/src/app/components/status-icon/status-icon.scss +++ b/ui/src/app/components/status-icon/status-icon.scss @@ -1,4 +1,4 @@ -@import 'node_modules/argo-ux/styles/colors'; +@import 'node_modules/argo-ui/v2/styles/colors'; .status-icon, .condition-icon { diff --git a/ui/src/app/components/status-icon/status-icon.tsx b/ui/src/app/components/status-icon/status-icon.tsx index 4bd4e4e7da..7f18813f4b 100644 --- a/ui/src/app/components/status-icon/status-icon.tsx +++ b/ui/src/app/components/status-icon/status-icon.tsx @@ -1,7 +1,4 @@ -import {faCheckCircle, faPauseCircle, faQuestionCircle, faTimesCircle} from '@fortawesome/free-regular-svg-icons'; -import {faArrowAltCircleDown, faCircleNotch} from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import {Tooltip} from 'argo-ux'; +import {Tooltip} from 'argo-ui/v2'; import * as React from 'react'; import './status-icon.scss'; @@ -18,32 +15,32 @@ export const StatusIcon = (props: {status: RolloutStatus}): JSX.Element => { const {status} = props; switch (status) { case 'Progressing': { - icon = faCircleNotch; + icon = 'fa-circle-notch'; className = 'progressing'; spin = true; break; } case 'Healthy': { - icon = faCheckCircle; + icon = 'fa-check-circle'; className = 'healthy'; break; } case 'Paused': { - icon = faPauseCircle; + icon = 'fa-pause-circle'; className = 'paused'; break; } case 'Degraded': { - icon = faTimesCircle; + icon = 'fa-times-circle'; className = 'degraded'; break; } default: { - icon = faQuestionCircle; + icon = 'fa-question-circle'; className = 'unknown'; } } - return ; + return ; }; export enum ReplicaSetStatus { @@ -61,34 +58,34 @@ export const ReplicaSetStatusIcon = (props: {status: ReplicaSetStatus}) => { switch (status) { case 'Healthy': case 'Running': { - icon = faCheckCircle; + icon = 'fa-check-circle'; className = 'healthy'; break; } case 'ScaledDown': { - icon = faArrowAltCircleDown; + icon = 'fa-arrow-alt-circle-down'; className = 'paused'; break; } case 'Degraded': { - icon = faTimesCircle; + icon = 'fa-times-circle'; className = 'degraded'; break; } case 'Progressing': { - icon = faCircleNotch; + icon = 'fa-circle-notch'; spin = true; className = 'progressing'; break; } default: { - icon = faQuestionCircle; + icon = 'fa-question-circle'; className = 'unknown'; } } return ( - + ); }; diff --git a/ui/src/app/shared/utils/utils.tsx b/ui/src/app/shared/utils/utils.tsx index d62628485d..c7f6d338a3 100644 --- a/ui/src/app/shared/utils/utils.tsx +++ b/ui/src/app/shared/utils/utils.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import * as moment from 'moment'; -import {faDove, faQuestion, faRunning, faSearch, faThumbsUp} from '@fortawesome/free-solid-svg-icons'; import {RolloutReplicaSetInfo} from '../../../models/rollout/generated'; export function useServerData(getData: () => Promise) { @@ -35,15 +34,15 @@ export enum ImageTag { export const IconForTag = (t?: ImageTag) => { switch (t) { case ImageTag.Canary: - return faDove; + return 'fa-dove'; case ImageTag.Stable: - return faThumbsUp; + return 'fa-thumbs-up'; case ImageTag.Preview: - return faSearch; + return 'fa-search'; case ImageTag.Active: - return faRunning; + return 'fa-running'; default: - return faQuestion; + return 'fa-question'; } }; diff --git a/ui/src/app/webpack.common.js b/ui/src/app/webpack.common.js index fbadca01c6..dbadf7a2f1 100644 --- a/ui/src/app/webpack.common.js +++ b/ui/src/app/webpack.common.js @@ -18,7 +18,7 @@ const config = { devtool: 'source-map', resolve: { - extensions: ['.ts', '.tsx', '.js', '.json', '.ttf'], + extensions: ['.ts', '.tsx', '.js', '.json'], alias: {react: require.resolve('react')}, }, @@ -36,10 +36,6 @@ const config = { test: /\.css$/, loader: 'style-loader!raw-loader', }, - { - test: /\.ttf$/, - use: ['file-loader'], - }, ], }, node: { @@ -55,6 +51,10 @@ const config = { new CopyWebpackPlugin({ patterns: [ {from: 'src/assets', to: 'assets'}, + { + from: 'node_modules/argo-ui/src/assets', + to: 'assets' + }, { from: 'node_modules/@fortawesome/fontawesome-free/webfonts', to: 'assets/fonts', diff --git a/ui/yarn.lock b/ui/yarn.lock index 87900f78ef..3551c8ca01 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -16,7 +16,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.5.5": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== @@ -50,29 +50,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@7.12.9": - version "7.12.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" - integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.5" - "@babel/parser" "^7.12.7" - "@babel/template" "^7.12.7" - "@babel/traverse" "^7.12.9" - "@babel/types" "^7.12.7" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.1" - json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4": +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.5", "@babel/core@^7.8.4": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.2.tgz#54e45334ffc0172048e5c93ded36461d3ad4c417" integrity sha512-OgC1mON+l4U4B4wiohJlQNUU3H73mpTyYY3j/c8U9dr9UagGGSm+WFpzjy/YLdoyjiG++c1kIDgxCo/mLwQJeQ== @@ -93,7 +71,7 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.12.1", "@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.14.2": +"@babel/generator@^7.12.1", "@babel/generator@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.2.tgz#d5773e8b557d421fd6ce0d5efa5fd7fc22567c30" integrity sha512-OnADYbKrffDVai5qcpkMxQ7caomHOoEwjkouqnN2QhydAjowFAZcsdecFIRUBdb+ZcruwYE4ythYmF1UBZU5xQ== @@ -127,7 +105,7 @@ browserslist "^4.14.5" semver "^6.3.0" -"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.14.0", "@babel/helper-create-class-features-plugin@^7.14.2": +"@babel/helper-create-class-features-plugin@^7.12.1", "@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.14.0": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.2.tgz#4e455b0329af29c2d3ad254b5dd5aed34595385d" integrity sha512-6YctwVsmlkchxfGUogvVrrhzyD3grFJyluj5JgDlQrwfMLJSt5tdAzFZfPf4H2Xoi5YLcQ6BxfJlaOBHuctyIw== @@ -147,20 +125,6 @@ "@babel/helper-annotate-as-pure" "^7.12.13" regexpu-core "^4.7.1" -"@babel/helper-define-polyfill-provider@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e" - integrity sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg== - dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - "@babel/helper-define-polyfill-provider@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz#a640051772045fedaaecc6f0c6c69f02bdd34bf1" @@ -241,11 +205,6 @@ dependencies: "@babel/types" "^7.12.13" -"@babel/helper-plugin-utils@7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" - integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== - "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" @@ -311,7 +270,7 @@ "@babel/traverse" "^7.13.0" "@babel/types" "^7.13.0" -"@babel/helpers@^7.12.1", "@babel/helpers@^7.12.5", "@babel/helpers@^7.14.0": +"@babel/helpers@^7.12.1", "@babel/helpers@^7.14.0": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62" integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg== @@ -329,7 +288,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.13", "@babel/parser@^7.12.3", "@babel/parser@^7.12.7", "@babel/parser@^7.14.2", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.12.3", "@babel/parser@^7.14.2", "@babel/parser@^7.7.0": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.2.tgz#0c1680aa44ad4605b16cbdcc5c341a61bde9c746" integrity sha512-IoVDIHpsgE/fu7eXBeRWt8zLbDrSvD7H1gpomOkPpBoEN8KCruCqSDdqo8dddwQQrui30KSvQBaMUOJiuFu6QQ== @@ -385,15 +344,6 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-decorators" "^7.12.1" -"@babel/plugin-proposal-decorators@^7.12.12": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.14.2.tgz#e68c3c5e4a6a08834456568256fc3e71b93590cf" - integrity sha512-LauAqDd/VjQDtae58QgBcEOE42NNP+jB2OE+XeC3KBI/E+BhhRjtr5viCIrj1hmu1YvrguLipIPRJZmS5yUcFw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.14.2" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/plugin-syntax-decorators" "^7.12.13" - "@babel/plugin-proposal-dynamic-import@^7.12.1", "@babel/plugin-proposal-dynamic-import@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz#01ebabd7c381cff231fa43e302939a9de5be9d9f" @@ -402,14 +352,6 @@ "@babel/helper-plugin-utils" "^7.13.0" "@babel/plugin-syntax-dynamic-import" "^7.8.3" -"@babel/plugin-proposal-export-default-from@^7.12.1": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.12.13.tgz#f110284108a9b2b96f01b15b3be9e54c2610a989" - integrity sha512-idIsBT+DGXdOHL82U+8bwX4goHm/z10g8sGGrQroh+HCRcm7mDv/luaGdWJQMTuCX2FsdXS7X0Nyyzp4znAPJA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - "@babel/plugin-syntax-export-default-from" "^7.12.13" - "@babel/plugin-proposal-export-namespace-from@^7.12.1", "@babel/plugin-proposal-export-namespace-from@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz#62542f94aa9ce8f6dba79eec698af22112253791" @@ -466,15 +408,6 @@ "@babel/helper-plugin-utils" "^7.13.0" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" - integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.2.tgz#e17d418f81cc103fedd4ce037e181c8056225abc" @@ -503,7 +436,7 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.12.1", "@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.14.2": +"@babel/plugin-proposal-optional-chaining@^7.12.1", "@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz#df8171a8b9c43ebf4c1dabe6311b432d83e1b34e" integrity sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA== @@ -566,7 +499,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-syntax-decorators@^7.12.1", "@babel/plugin-syntax-decorators@^7.12.13": +"@babel/plugin-syntax-decorators@^7.12.1": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.13.tgz#fac829bf3c7ef4a1bc916257b403e58c6bdaf648" integrity sha512-Rw6aIXGuqDLr6/LoBBYE57nKOzQpz/aDkKlMqEwH+Vp0MXbG6H/TfRjaY343LKxzAKAMXIHsQ8JzaZKuDZ9MwA== @@ -580,13 +513,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-export-default-from@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.12.13.tgz#3c807d37efaf0a806f1deb556ccb3b2f562ae9c2" - integrity sha512-gVry0zqoums0hA+EniCYK3gABhjYSLX1dVuwYpPw9DrLNA4/GovXySHVg4FGRsZht09ON/5C2NVx3keq+qqVGQ== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - "@babel/plugin-syntax-export-namespace-from@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" @@ -615,13 +541,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@7.12.1": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" - integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-jsx@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15" @@ -650,7 +569,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": +"@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== @@ -715,7 +634,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-transform-block-scoping@^7.12.1", "@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.14.2": +"@babel/plugin-transform-block-scoping@^7.12.1", "@babel/plugin-transform-block-scoping@^7.14.2": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.2.tgz#761cb12ab5a88d640ad4af4aa81f820e6b5fdf5c" integrity sha512-neZZcP19NugZZqNwMTH+KoBjx5WyvESPSIOQb4JHpfd+zPfqcH65RMu5xJju5+6q/Y2VzYrleQTr+b6METyyxg== @@ -925,7 +844,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.13.0" -"@babel/plugin-transform-react-jsx@^7.12.1", "@babel/plugin-transform-react-jsx@^7.12.12", "@babel/plugin-transform-react-jsx@^7.12.17", "@babel/plugin-transform-react-jsx@^7.13.12": +"@babel/plugin-transform-react-jsx@^7.12.1", "@babel/plugin-transform-react-jsx@^7.12.17", "@babel/plugin-transform-react-jsx@^7.13.12": version "7.13.12" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz#1df5dfaf0f4b784b43e96da6f28d630e775f68b3" integrity sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA== @@ -1004,7 +923,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-transform-typescript@^7.12.1", "@babel/plugin-transform-typescript@^7.13.0": +"@babel/plugin-transform-typescript@^7.12.1": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz#4a498e1f3600342d2a9e61f60131018f55774853" integrity sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ== @@ -1100,7 +1019,7 @@ core-js-compat "^3.6.2" semver "^5.5.0" -"@babel/preset-env@^7.12.1", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.8.4": +"@babel/preset-env@^7.12.1", "@babel/preset-env@^7.8.4": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.2.tgz#e80612965da73579c84ad2f963c2359c71524ed5" integrity sha512-7dD7lVT8GMrE73v4lvDEb85cgcQhdES91BSD7jS/xjC6QY8PnRhux35ac+GCpbiRhp8crexBvZZqnaL6VrY8TQ== @@ -1203,7 +1122,7 @@ "@babel/plugin-transform-react-jsx-source" "^7.12.1" "@babel/plugin-transform-react-pure-annotations" "^7.12.1" -"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.12.5": +"@babel/preset-react@^7.12.5": version "7.13.13" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.13.13.tgz#fa6895a96c50763fe693f9148568458d5a839761" integrity sha512-gx+tDLIE06sRjKJkVtpZ/t3mzCDOnPG+ggHZG9lffUbX8+wC739x20YQc9V35Do6ZAxaUc/HhVHIiOzz5MvDmA== @@ -1223,26 +1142,6 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-typescript" "^7.12.1" -"@babel/preset-typescript@^7.12.7": - version "7.13.0" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz#ab107e5f050609d806fbb039bec553b33462c60a" - integrity sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw== - dependencies: - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/helper-validator-option" "^7.12.17" - "@babel/plugin-transform-typescript" "^7.13.0" - -"@babel/register@^7.12.1": - version "7.13.16" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.13.16.tgz#ae3ab0b55c8ec28763877383c454f01521d9a53d" - integrity sha512-dh2t11ysujTwByQjXNgJ48QZ2zcXKQVdV8s0TbeMI0flmtGWCdTwK9tJiACHXPLmncm5+ktNn/diojA45JE4jg== - dependencies: - clone-deep "^4.0.1" - find-cache-dir "^2.0.0" - make-dir "^2.1.0" - pirates "^4.0.0" - source-map-support "^0.5.16" - "@babel/runtime-corejs3@^7.10.2": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz#6bf5fbc0b961f8e3202888cb2cd0fb7a0a9a3f66" @@ -1258,14 +1157,21 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.17", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.14.0" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6" integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA== dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.12.7", "@babel/template@^7.3.3": +"@babel/runtime@^7.4.2", "@babel/runtime@^7.8.7": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== @@ -1274,7 +1180,7 @@ "@babel/parser" "^7.12.13" "@babel/types" "^7.12.13" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.7.0": +"@babel/traverse@^7.1.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2", "@babel/traverse@^7.7.0": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== @@ -1288,7 +1194,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.16", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.6", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.16", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.14.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.2.tgz#4208ae003107ef8a057ea8333e56eb64d2f6a2c3" integrity sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw== @@ -1296,11 +1202,6 @@ "@babel/helper-validator-identifier" "^7.14.0" to-fast-properties "^2.0.0" -"@base2/pretty-print-object@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" - integrity sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw== - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1324,141 +1225,11 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== -"@definitelytyped/header-parser@latest": - version "0.0.81" - resolved "https://registry.yarnpkg.com/@definitelytyped/header-parser/-/header-parser-0.0.81.tgz#3a96e7bb104715d0476f18f1e04497d66adf44fe" - integrity sha512-nsWJatoIvRbQL9ZKpTQqk1sp4FE4TAYPeFIFGRpexvLU0j30XwX0K76Zq7Nm6oxVUvXBjLyZDIVOWF7tol9sMA== - dependencies: - "@definitelytyped/typescript-versions" "^0.0.81" - "@types/parsimmon" "^1.10.1" - parsimmon "^1.13.0" - -"@definitelytyped/typescript-versions@^0.0.81", "@definitelytyped/typescript-versions@latest": - version "0.0.81" - resolved "https://registry.yarnpkg.com/@definitelytyped/typescript-versions/-/typescript-versions-0.0.81.tgz#c139ff1352380c52afef4733ec7b2306f567d39d" - integrity sha512-STXwuKjE32PQ+NJ+5/aH+g1DjNeZ3mcONmxlmIBsNShxlSXJ3dkStXf8sh4OSRQpvj9d1c/vKBa/ILg/67Uoxg== - -"@definitelytyped/utils@latest": - version "0.0.81" - resolved "https://registry.yarnpkg.com/@definitelytyped/utils/-/utils-0.0.81.tgz#8cbd9a8e61e162f4888187880faf81314cbc4993" - integrity sha512-1Outdd2c+U+yHQvERri2vBwKjf7myd9FKk885iiI7sD4T7Ld25xrcsb30POrMgdXYh3ZmODeo2CwkzZPNxtdSQ== - dependencies: - "@definitelytyped/typescript-versions" "^0.0.81" - "@types/node" "^14.14.35" - charm "^1.0.2" - fs-extra "^8.1.0" - fstream "^1.0.12" - npm-registry-client "^8.6.0" - tar "^2.2.2" - tar-stream "^2.1.4" - "@discoveryjs/json-ext@^0.5.0": version "0.5.2" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== -"@emotion/cache@^10.0.27": - version "10.0.29" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" - integrity sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ== - dependencies: - "@emotion/sheet" "0.9.4" - "@emotion/stylis" "0.8.5" - "@emotion/utils" "0.11.3" - "@emotion/weak-memoize" "0.2.5" - -"@emotion/core@^10.1.1": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" - integrity sha512-ZMLG6qpXR8x031NXD8HJqugy/AZSkAuMxxqB46pmAR7ze47MhNJ56cdoX243QPZdGctrdfo+s08yZTiwaUcRKA== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/cache" "^10.0.27" - "@emotion/css" "^10.0.27" - "@emotion/serialize" "^0.11.15" - "@emotion/sheet" "0.9.4" - "@emotion/utils" "0.11.3" - -"@emotion/css@^10.0.27": - version "10.0.27" - resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" - integrity sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw== - dependencies: - "@emotion/serialize" "^0.11.15" - "@emotion/utils" "0.11.3" - babel-plugin-emotion "^10.0.27" - -"@emotion/hash@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" - integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== - -"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.6": - version "0.8.8" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" - integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== - dependencies: - "@emotion/memoize" "0.7.4" - -"@emotion/memoize@0.7.4": - version "0.7.4" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" - integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== - -"@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": - version "0.11.16" - resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" - integrity sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg== - dependencies: - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/unitless" "0.7.5" - "@emotion/utils" "0.11.3" - csstype "^2.5.7" - -"@emotion/sheet@0.9.4": - version "0.9.4" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" - integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== - -"@emotion/styled-base@^10.0.27": - version "10.0.31" - resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" - integrity sha512-wTOE1NcXmqMWlyrtwdkqg87Mu6Rj1MaukEoEmEkHirO5IoHDJ8LgCQL4MjJODgxWxXibGR3opGp1p7YvkNEdXQ== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/is-prop-valid" "0.8.8" - "@emotion/serialize" "^0.11.15" - "@emotion/utils" "0.11.3" - -"@emotion/styled@^10.0.27": - version "10.0.27" - resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-10.0.27.tgz#12cb67e91f7ad7431e1875b1d83a94b814133eaf" - integrity sha512-iK/8Sh7+NLJzyp9a5+vIQIXTYxfT4yB/OJbjzQanB2RZpvmzBQOHZWhpAMZWYEKRNNbsD6WfBw5sVWkb6WzS/Q== - dependencies: - "@emotion/styled-base" "^10.0.27" - babel-plugin-emotion "^10.0.27" - -"@emotion/stylis@0.8.5": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" - integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== - -"@emotion/unitless@0.7.5": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" - integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== - -"@emotion/utils@0.11.3": - version "0.11.3" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" - integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== - -"@emotion/weak-memoize@0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" - integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== - "@eslint/eslintrc@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14" @@ -1474,43 +1245,10 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@fortawesome/fontawesome-common-types@^0.2.35": - version "0.2.35" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz#01dd3d054da07a00b764d78748df20daf2b317e9" - integrity sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw== - -"@fortawesome/fontawesome-free@^5.15.2": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a" - integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w== - -"@fortawesome/fontawesome-svg-core@^1.2.34": - version "1.2.35" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz#85aea8c25645fcec88d35f2eb1045c38d3e65cff" - integrity sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg== - dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.35" - -"@fortawesome/free-regular-svg-icons@^5.15.2": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz#1ec4f2410ff638db549c5c5484fc60b66407dbe6" - integrity sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ== - dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.35" - -"@fortawesome/free-solid-svg-icons@^5.15.2": - version "5.15.3" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz#52eebe354f60dc77e0bde934ffc5c75ffd04f9d8" - integrity sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q== - dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.35" - -"@fortawesome/react-fontawesome@^0.1.14": - version "0.1.14" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz#bf28875c3935b69ce2dc620e1060b217a47f64ca" - integrity sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA== - dependencies: - prop-types "^15.7.2" +"@fortawesome/fontawesome-free@^5.8.1": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5" + integrity sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg== "@hapi/address@2.x.x": version "2.1.4" @@ -1731,58 +1469,6 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@mdx-js/loader@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4" - integrity sha512-9CjGwy595NaxAYp0hF9B/A0lH6C8Rms97e2JS9d3jVUtILn6pT5i5IV965ra3lIWc7Rs1GG1tBdVF7dCowYe6Q== - dependencies: - "@mdx-js/mdx" "1.6.22" - "@mdx-js/react" "1.6.22" - loader-utils "2.0.0" - -"@mdx-js/mdx@1.6.22", "@mdx-js/mdx@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" - integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== - dependencies: - "@babel/core" "7.12.9" - "@babel/plugin-syntax-jsx" "7.12.1" - "@babel/plugin-syntax-object-rest-spread" "7.8.3" - "@mdx-js/util" "1.6.22" - babel-plugin-apply-mdx-type-prop "1.6.22" - babel-plugin-extract-import-names "1.6.22" - camelcase-css "2.0.1" - detab "2.0.4" - hast-util-raw "6.0.1" - lodash.uniq "4.5.0" - mdast-util-to-hast "10.0.1" - remark-footnotes "2.0.0" - remark-mdx "1.6.22" - remark-parse "8.0.3" - remark-squeeze-paragraphs "4.0.0" - style-to-object "0.3.0" - unified "9.2.0" - unist-builder "2.0.3" - unist-util-visit "2.0.3" - -"@mdx-js/react@1.6.22", "@mdx-js/react@^1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" - integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== - -"@mdx-js/util@1.6.22": - version "1.6.22" - resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" - integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== - -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - "@nodelib/fs.scandir@2.1.4": version "2.1.4" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" @@ -1796,11 +1482,6 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== - "@nodelib/fs.walk@^1.2.3": version "1.2.6" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" @@ -1834,21 +1515,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28" integrity sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ== -"@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" - integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== - -"@reach/router@^1.3.4": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@reach/router/-/router-1.3.4.tgz#d2574b19370a70c80480ed91f3da840136d10f8c" - integrity sha512-+mtn9wjlB9NN2CNnnC/BRYtwdKBfSyyasPYraNAyvaV1occr/5NnB4CVzjEZipNHwYebQwcndGUmpFzxAUoqSA== - dependencies: - create-react-context "0.3.0" - invariant "^2.2.3" - prop-types "^15.6.1" - react-lifecycles-compat "^3.0.4" - "@rollup/plugin-node-resolve@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz#80de384edfbd7bfc9101164910f86078151a3eca" @@ -1891,624 +1557,6 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@storybook/addon-actions@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.2.9.tgz#688413ac77410690755a5da3c277bfa0ff1a10b0" - integrity sha512-CkUYSMt+fvuHfWvtDzlhhaeQBCWlUo99xdL88JTsTml05P43bIHZNIRv2QJ8DwhHuxdIPeHKLmz9y/ymOagOnw== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/theming" "6.2.9" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.20" - polished "^4.0.5" - prop-types "^15.7.2" - react-inspector "^5.1.0" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - uuid-browser "^3.1.0" - -"@storybook/addon-backgrounds@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.2.9.tgz#4f75aa58b262f461d9f8713d65d11407f4e53537" - integrity sha512-oPSdeoUuvaXshY5sQRagbYXpr6ZEVUuLhGYBnZTlvm19QMeNCXQE+rdlgzcgyafq4mc1FI/udE2MpJ1dhfS6pQ== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/theming" "6.2.9" - core-js "^3.8.2" - global "^4.4.0" - memoizerific "^1.11.3" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/addon-controls@6.2.9", "@storybook/addon-controls@^6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.2.9.tgz#eeec14b2946f1fb5326115f2205ed72c7f44ccea" - integrity sha512-NvXAJ7I5U4CLxv4wL3/Ne9rehJlgnSmQlLIG/z6dg5zm7JIb48LT4IY6GzjlUP5LkjmO9KJ8gJC249uRt2iPBQ== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/node-logger" "6.2.9" - "@storybook/theming" "6.2.9" - core-js "^3.8.2" - ts-dedent "^2.0.0" - -"@storybook/addon-docs@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.2.9.tgz#61271e54ff4ea490409e4873ed022e62577366c1" - integrity sha512-qOtwgiqI3LMqT0eXYNV6ykp7qSu0LQGeXxy3wOBGuDDqAizfgnAjomYEWGFcyKp5ahV7HCRCjxbixAklFPUmyw== - dependencies: - "@babel/core" "^7.12.10" - "@babel/generator" "^7.12.11" - "@babel/parser" "^7.12.11" - "@babel/plugin-transform-react-jsx" "^7.12.12" - "@babel/preset-env" "^7.12.11" - "@jest/transform" "^26.6.2" - "@mdx-js/loader" "^1.6.22" - "@mdx-js/mdx" "^1.6.22" - "@mdx-js/react" "^1.6.22" - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/builder-webpack4" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/csf" "0.0.1" - "@storybook/node-logger" "6.2.9" - "@storybook/postinstall" "6.2.9" - "@storybook/source-loader" "6.2.9" - "@storybook/theming" "6.2.9" - acorn "^7.4.1" - acorn-jsx "^5.3.1" - acorn-walk "^7.2.0" - core-js "^3.8.2" - doctrine "^3.0.0" - escodegen "^2.0.0" - fast-deep-equal "^3.1.3" - global "^4.4.0" - html-tags "^3.1.0" - js-string-escape "^1.0.1" - loader-utils "^2.0.0" - lodash "^4.17.20" - prettier "~2.2.1" - prop-types "^15.7.2" - react-element-to-jsx-string "^14.3.2" - regenerator-runtime "^0.13.7" - remark-external-links "^8.0.0" - remark-slug "^6.0.0" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/addon-essentials@^6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.2.9.tgz#cd946b024804c4d9bfec4e232b74ffdf936b25ef" - integrity sha512-zXsV4e1TCkHyDwi7hew4h9eJfDW++f2BNKzTif+DAcjPUVFDp7yC17gLjS5IhOjcQk+db0UUlFSx/OrTxhy7Xw== - dependencies: - "@storybook/addon-actions" "6.2.9" - "@storybook/addon-backgrounds" "6.2.9" - "@storybook/addon-controls" "6.2.9" - "@storybook/addon-docs" "6.2.9" - "@storybook/addon-toolbars" "6.2.9" - "@storybook/addon-viewport" "6.2.9" - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/node-logger" "6.2.9" - core-js "^3.8.2" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - -"@storybook/addon-toolbars@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.2.9.tgz#29f2f4cba0bfbcff9424958eb573e537f7e2d5af" - integrity sha512-4WjIofN5npBPNZ8v1UhzPeATB9RnAWRH/y1AVS1HB+zl6Ku92o7aOMqVxs8zR1oSSmtkHh/rcUcpATFKjuofdw== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/components" "6.2.9" - core-js "^3.8.2" - -"@storybook/addon-viewport@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.2.9.tgz#e380de567cea6c24c4e933efa009e80428d5b49e" - integrity sha512-IK2mu5njmfcAT967SJtBOY2B6NPMikySZga9QuaLdSpQxPd3vXKNMVG1CjnduMLeDaAoUlvlJISeEPbYGuE+1A== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/theming" "6.2.9" - core-js "^3.8.2" - global "^4.4.0" - memoizerific "^1.11.3" - prop-types "^15.7.2" - regenerator-runtime "^0.13.7" - -"@storybook/addons@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.2.9.tgz#b7ba2b9f0e15b852c7d6b57d04fb0a493c57477c" - integrity sha512-GnmEKbJwiN1jncN9NSA8CuR1i2XAlasPcl/Zn0jkfV9WitQeczVcJCPw86SGH84AD+tTBCyF2i9UC0KaOV1YBQ== - dependencies: - "@storybook/api" "6.2.9" - "@storybook/channels" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/router" "6.2.9" - "@storybook/theming" "6.2.9" - core-js "^3.8.2" - global "^4.4.0" - regenerator-runtime "^0.13.7" - -"@storybook/api@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.2.9.tgz#a9b46569192ad5d8da6435c9d63dc4b0c8463b51" - integrity sha512-okkA3HAScE9tGnYBrjTOcgzT+L1lRHNoEh3ZfGgh1u/XNEyHGNkj4grvkd6nX7BzRcYQ/l2VkcKCqmOjUnSkVQ== - dependencies: - "@reach/router" "^1.3.4" - "@storybook/channels" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/csf" "0.0.1" - "@storybook/router" "6.2.9" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.2.9" - "@types/reach__router" "^1.3.7" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.20" - memoizerific "^1.11.3" - qs "^6.10.0" - regenerator-runtime "^0.13.7" - store2 "^2.12.0" - telejson "^5.1.0" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/builder-webpack4@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.2.9.tgz#dddff0b1b4590a7ba088ce13e7cc42e482f6455d" - integrity sha512-swECic1huVdj+B+iRJIQ8ds59HuPVE4fmhI+j/nhw0CQCsgAEKqDlOQVYEimW6nZX8GO4WxNm6tiiRzxixejbw== - dependencies: - "@babel/core" "^7.12.10" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-decorators" "^7.12.12" - "@babel/plugin-proposal-export-default-from" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.7" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.12" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/preset-env" "^7.12.11" - "@babel/preset-react" "^7.12.10" - "@babel/preset-typescript" "^7.12.7" - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/channel-postmessage" "6.2.9" - "@storybook/channels" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core-common" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/node-logger" "6.2.9" - "@storybook/router" "6.2.9" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.2.9" - "@storybook/ui" "6.2.9" - "@types/node" "^14.0.10" - "@types/webpack" "^4.41.26" - autoprefixer "^9.8.6" - babel-loader "^8.2.2" - babel-plugin-macros "^2.8.0" - babel-plugin-polyfill-corejs3 "^0.1.0" - case-sensitive-paths-webpack-plugin "^2.3.0" - core-js "^3.8.2" - css-loader "^3.6.0" - dotenv-webpack "^1.8.0" - file-loader "^6.2.0" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^4.1.6" - fs-extra "^9.0.1" - glob "^7.1.6" - glob-promise "^3.4.0" - global "^4.4.0" - html-webpack-plugin "^4.0.0" - pnp-webpack-plugin "1.6.4" - postcss "^7.0.35" - postcss-flexbugs-fixes "^4.2.1" - postcss-loader "^4.2.0" - raw-loader "^4.0.2" - react-dev-utils "^11.0.3" - stable "^0.1.8" - style-loader "^1.3.0" - terser-webpack-plugin "^3.1.0" - ts-dedent "^2.0.0" - url-loader "^4.1.1" - util-deprecate "^1.0.2" - webpack "4" - webpack-dev-middleware "^3.7.3" - webpack-filter-warnings-plugin "^1.2.1" - webpack-hot-middleware "^2.25.0" - webpack-virtual-modules "^0.2.2" - -"@storybook/channel-postmessage@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.2.9.tgz#ad85573e0a5d6f0cde3504f168d87a73cb0b6269" - integrity sha512-OqV+gLeeCHR0KExsIz0B7gD17Cjd9D+I75qnBsLWM9inWO5kc/WZ5svw8Bvjlcm6snWpvxUaT8L+svuqcPSmww== - dependencies: - "@storybook/channels" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/core-events" "6.2.9" - core-js "^3.8.2" - global "^4.4.0" - qs "^6.10.0" - telejson "^5.1.0" - -"@storybook/channels@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.2.9.tgz#a9fd7f25102cbec15fb56f76abf891b7b214e9de" - integrity sha512-6dC8Fb2ipNyOQXnUZMDeEUaJGH5DMLzyHlGLhVyDtrO5WR6bO8mQdkzf4+5dSKXgCBNX0BSkssXth4pDjn18rg== - dependencies: - core-js "^3.8.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/client-api@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.2.9.tgz#f0bb44e9b2692adfbf30d7ff751c6dd44bcfe1ce" - integrity sha512-aLvEUVkbvv6Qo/2mF4rFCecdqi2CGOUDdsV1a6EFIVS/9gXFdpirsOwKHo9qNjacGdWPlBYGCUcbrw+DvNaSFA== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/channel-postmessage" "6.2.9" - "@storybook/channels" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/csf" "0.0.1" - "@types/qs" "^6.9.5" - "@types/webpack-env" "^1.16.0" - core-js "^3.8.2" - global "^4.4.0" - lodash "^4.17.20" - memoizerific "^1.11.3" - qs "^6.10.0" - regenerator-runtime "^0.13.7" - stable "^0.1.8" - store2 "^2.12.0" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/client-logger@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.2.9.tgz#77c1ea39684ad2a2cf6836051b381fc5b354e132" - integrity sha512-IfOQZuvpjh66qBInQCJOb9S0dTGpzZ/Cxlcvokp+PYt95KztaWN3mPm+HaDQCeRsrWNe0Bpm1zuickcJ6dBOXg== - dependencies: - core-js "^3.8.2" - global "^4.4.0" - -"@storybook/components@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.2.9.tgz#7189f9715b05720fe083ae8ad014849f14e98e73" - integrity sha512-hnV1MI2aB2g1sJ7NJphpxi7TwrMZQ/tpCJeHnkjmzyC6ez1MXqcBXGrEEdSXzRfAxjQTOEpu6H1mnns0xMP0Ag== - dependencies: - "@popperjs/core" "^2.6.0" - "@storybook/client-logger" "6.2.9" - "@storybook/csf" "0.0.1" - "@storybook/theming" "6.2.9" - "@types/color-convert" "^2.0.0" - "@types/overlayscrollbars" "^1.12.0" - "@types/react-syntax-highlighter" "11.0.5" - color-convert "^2.0.1" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.20" - markdown-to-jsx "^7.1.0" - memoizerific "^1.11.3" - overlayscrollbars "^1.13.1" - polished "^4.0.5" - prop-types "^15.7.2" - react-colorful "^5.0.1" - react-popper-tooltip "^3.1.1" - react-syntax-highlighter "^13.5.3" - react-textarea-autosize "^8.3.0" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - -"@storybook/core-client@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.2.9.tgz#3f611947e64dee0a297e512ff974087bc52c1877" - integrity sha512-jW841J5lCe1Ub5ZMtzYPgCy/OUddFxxVYeHLZyuNxlH5RoiQQxbDpuFlzuZMYGuIzD6eZw+ANE4w5vW/y5oBfA== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/channel-postmessage" "6.2.9" - "@storybook/client-api" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/csf" "0.0.1" - "@storybook/ui" "6.2.9" - ansi-to-html "^0.6.11" - core-js "^3.8.2" - global "^4.4.0" - lodash "^4.17.20" - qs "^6.10.0" - regenerator-runtime "^0.13.7" - ts-dedent "^2.0.0" - unfetch "^4.2.0" - util-deprecate "^1.0.2" - -"@storybook/core-common@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.2.9.tgz#54f8e005733d39c4cb90eec7c17f9ca4dcbeec5f" - integrity sha512-ve0Qb4EMit8jGibfZBprmaU2i4LtpB4vSMIzD9nB1YeBmw2cGhHubtmayZ0TwcV3fPQhtYH9wwRWuWyzzHyQyw== - dependencies: - "@babel/core" "^7.12.10" - "@babel/plugin-proposal-class-properties" "^7.12.1" - "@babel/plugin-proposal-decorators" "^7.12.12" - "@babel/plugin-proposal-export-default-from" "^7.12.1" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" - "@babel/plugin-proposal-object-rest-spread" "^7.12.1" - "@babel/plugin-proposal-optional-chaining" "^7.12.7" - "@babel/plugin-proposal-private-methods" "^7.12.1" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.12.1" - "@babel/plugin-transform-block-scoping" "^7.12.12" - "@babel/plugin-transform-classes" "^7.12.1" - "@babel/plugin-transform-destructuring" "^7.12.1" - "@babel/plugin-transform-for-of" "^7.12.1" - "@babel/plugin-transform-parameters" "^7.12.1" - "@babel/plugin-transform-shorthand-properties" "^7.12.1" - "@babel/plugin-transform-spread" "^7.12.1" - "@babel/preset-env" "^7.12.11" - "@babel/preset-react" "^7.12.10" - "@babel/preset-typescript" "^7.12.7" - "@babel/register" "^7.12.1" - "@storybook/node-logger" "6.2.9" - "@storybook/semver" "^7.3.2" - "@types/glob-base" "^0.3.0" - "@types/micromatch" "^4.0.1" - "@types/node" "^14.0.10" - "@types/pretty-hrtime" "^1.0.0" - babel-loader "^8.2.2" - babel-plugin-macros "^3.0.1" - babel-plugin-polyfill-corejs3 "^0.1.0" - chalk "^4.1.0" - core-js "^3.8.2" - express "^4.17.1" - file-system-cache "^1.0.5" - find-up "^5.0.0" - fork-ts-checker-webpack-plugin "^6.0.4" - glob "^7.1.6" - glob-base "^0.3.0" - interpret "^2.2.0" - json5 "^2.1.3" - lazy-universal-dotenv "^3.0.1" - micromatch "^4.0.2" - pkg-dir "^5.0.0" - pretty-hrtime "^1.0.3" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - webpack "4" - -"@storybook/core-events@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.2.9.tgz#4f12947cd15d1eb3c4109923657c012feef521cd" - integrity sha512-xQmbX/oYQK1QsAGN8hriXX5SUKOoTUe3L4dVaVHxJqy7MReRWJpprJmCpbAPJzWS6WCbDFfCM5kVEexHLOzJlQ== - dependencies: - core-js "^3.8.2" - -"@storybook/core-server@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.2.9.tgz#da8b7f043ff59ee6cd2e8631ba8d0f954fdc265a" - integrity sha512-DzihO73pj1Ro0Y4tq9hjw2mLMUYeSRPrx7CndCOBxcTHCKQ8Kd7Dee3wJ49t5/19V7TW1+4lYR59GAy73FeOAQ== - dependencies: - "@babel/core" "^7.12.10" - "@babel/plugin-transform-template-literals" "^7.12.1" - "@babel/preset-react" "^7.12.10" - "@storybook/addons" "6.2.9" - "@storybook/builder-webpack4" "6.2.9" - "@storybook/core-client" "6.2.9" - "@storybook/core-common" "6.2.9" - "@storybook/node-logger" "6.2.9" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.2.9" - "@storybook/ui" "6.2.9" - "@types/node" "^14.0.10" - "@types/node-fetch" "^2.5.7" - "@types/pretty-hrtime" "^1.0.0" - "@types/webpack" "^4.41.26" - airbnb-js-shims "^2.2.1" - babel-loader "^8.2.2" - better-opn "^2.1.1" - boxen "^4.2.0" - case-sensitive-paths-webpack-plugin "^2.3.0" - chalk "^4.1.0" - cli-table3 "0.6.0" - commander "^6.2.1" - core-js "^3.8.2" - cpy "^8.1.1" - css-loader "^3.6.0" - detect-port "^1.3.0" - dotenv-webpack "^1.8.0" - express "^4.17.1" - file-loader "^6.2.0" - file-system-cache "^1.0.5" - find-up "^5.0.0" - fs-extra "^9.0.1" - global "^4.4.0" - html-webpack-plugin "^4.0.0" - ip "^1.1.5" - node-fetch "^2.6.1" - pnp-webpack-plugin "1.6.4" - pretty-hrtime "^1.0.3" - prompts "^2.4.0" - read-pkg-up "^7.0.1" - regenerator-runtime "^0.13.7" - resolve-from "^5.0.0" - serve-favicon "^2.5.0" - style-loader "^1.3.0" - telejson "^5.1.0" - terser-webpack-plugin "^3.1.0" - ts-dedent "^2.0.0" - url-loader "^4.1.1" - util-deprecate "^1.0.2" - webpack "4" - webpack-dev-middleware "^3.7.3" - webpack-virtual-modules "^0.2.2" - -"@storybook/core@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.2.9.tgz#e32e72b3bdb44384f5f0ff93ad1a483acd033b4b" - integrity sha512-pzbyjWvj0t8m0kR2pC9GQne4sZn7Y/zfcbm6/31CL+yhzOQjfJEj3n4ZFUlxikXqQJPg1aWfypfyaeaLL0QyuA== - dependencies: - "@storybook/core-client" "6.2.9" - "@storybook/core-server" "6.2.9" - -"@storybook/csf@0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.1.tgz#95901507dc02f0bc6f9ac8ee1983e2fc5bb98ce6" - integrity sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw== - dependencies: - lodash "^4.17.15" - -"@storybook/node-logger@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.2.9.tgz#c67d8d7684514b8d00207502e8a9adda0ee750e5" - integrity sha512-ryRBChWZf1A5hOVONErJZosS25IdMweoMVFAUAcj91iC0ynoSA6YL2jmoE71jQchxEXEgkDeRkX9lR/GlqFGZQ== - dependencies: - "@types/npmlog" "^4.1.2" - chalk "^4.1.0" - core-js "^3.8.2" - npmlog "^4.1.2" - pretty-hrtime "^1.0.3" - -"@storybook/postinstall@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.2.9.tgz#3573ca86a27e9628defdd3a2c64721ee9db359ce" - integrity sha512-HjAjXZV+WItonC7lVrfrUsQuRFZNz1g1lE0GgsEK2LdC5rAcD/JwJxjiWREwY+RGxKL9rpWgqyxVQajpIJRjhA== - dependencies: - core-js "^3.8.2" - -"@storybook/router@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.2.9.tgz#547543031dd8330870bb6b473dcf7e51982e841c" - integrity sha512-7Bn1OFoItCl8whXRT8N1qp1Lky7kzXJ3aslWp5E8HcM8rxh4OYXfbaeiyJEJxBTGC5zxgY+tAEXHFjsAviFROg== - dependencies: - "@reach/router" "^1.3.4" - "@storybook/client-logger" "6.2.9" - "@types/reach__router" "^1.3.7" - core-js "^3.8.2" - fast-deep-equal "^3.1.3" - global "^4.4.0" - lodash "^4.17.20" - memoizerific "^1.11.3" - qs "^6.10.0" - ts-dedent "^2.0.0" - -"@storybook/semver@^7.3.2": - version "7.3.2" - resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" - integrity sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg== - dependencies: - core-js "^3.6.5" - find-up "^4.1.0" - -"@storybook/source-loader@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.2.9.tgz#ac6b314e48044acad5318d237275b24e684edb9f" - integrity sha512-cx499g7BG2oeXvRFx45r0W0p2gKEy/e88WsUFnqqfMKZBJ8K0R/lx5DI0l1hq+TzSrE6uGe0/uPlaLkJNIro7g== - dependencies: - "@storybook/addons" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/csf" "0.0.1" - core-js "^3.8.2" - estraverse "^5.2.0" - global "^4.4.0" - loader-utils "^2.0.0" - lodash "^4.17.20" - prettier "~2.2.1" - regenerator-runtime "^0.13.7" - -"@storybook/theming@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.2.9.tgz#16bf40180861f222c7ed1d80abd5d1e3cb315660" - integrity sha512-183oJW7AD7Fhqg5NT4ct3GJntwteAb9jZnQ6yhf9JSdY+fk8OhxRbPf7ov0au2gYACcGrWDd9K5pYQsvWlP5gA== - dependencies: - "@emotion/core" "^10.1.1" - "@emotion/is-prop-valid" "^0.8.6" - "@emotion/styled" "^10.0.27" - "@storybook/client-logger" "6.2.9" - core-js "^3.8.2" - deep-object-diff "^1.1.0" - emotion-theming "^10.0.27" - global "^4.4.0" - memoizerific "^1.11.3" - polished "^4.0.5" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" - -"@storybook/ui@6.2.9": - version "6.2.9" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.2.9.tgz#25cdf7ae2ef38ab337570c2377fda1da999792e7" - integrity sha512-jq2xmw3reIqik/6ibUSbNKGR+Xvr9wkAEwexiOl+5WQ5BeYJpw4dmDmsFQf+SQuWaSEUUPolbzkakRQM778Kdg== - dependencies: - "@emotion/core" "^10.1.1" - "@storybook/addons" "6.2.9" - "@storybook/api" "6.2.9" - "@storybook/channels" "6.2.9" - "@storybook/client-logger" "6.2.9" - "@storybook/components" "6.2.9" - "@storybook/core-events" "6.2.9" - "@storybook/router" "6.2.9" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.2.9" - "@types/markdown-to-jsx" "^6.11.3" - copy-to-clipboard "^3.3.1" - core-js "^3.8.2" - core-js-pure "^3.8.2" - downshift "^6.0.15" - emotion-theming "^10.0.27" - fuse.js "^3.6.1" - global "^4.4.0" - lodash "^4.17.20" - markdown-to-jsx "^6.11.4" - memoizerific "^1.11.3" - polished "^4.0.5" - qs "^6.10.0" - react-draggable "^4.4.3" - react-helmet-async "^1.0.7" - react-sizeme "^3.0.1" - regenerator-runtime "^0.13.7" - resolve-from "^5.0.0" - store2 "^2.12.0" - "@surma/rollup-plugin-off-main-thread@^1.1.1": version "1.4.2" resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58" @@ -2663,6 +1711,14 @@ dependencies: "@babel/runtime" "^7.12.5" +"@tippy.js/react@^2.1.2": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@tippy.js/react/-/react-2.2.3.tgz#2ffb0af6693055be7db4b329b2d3cc7f2356f68e" + integrity sha512-5XYvbQujzDj9r00JYEz/cBtm6DutjOdv2azdco53B+eWF7FDBCQfkLVn87wimfEpmGK0vqRQv/cwFxFcoOP98Q== + dependencies: + prop-types "^15.6.2" + tippy.js "^4.3.4" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -2706,23 +1762,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/braces@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" - integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== - -"@types/color-convert@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22" - integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ== - dependencies: - "@types/color-name" "*" - -"@types/color-name@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" - integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== - "@types/eslint@^7.2.6": version "7.2.10" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.10.tgz#4b7a9368d46c0f8cd5408c23288a59aa2394d917" @@ -2741,12 +1780,7 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/glob-base@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d" - integrity sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0= - -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== @@ -2761,13 +1795,6 @@ dependencies: "@types/node" "*" -"@types/hast@^2.0.0": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.1.tgz#b16872f2a6144c7025f296fb9636a667ebb79cd9" - integrity sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q== - dependencies: - "@types/unist" "*" - "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" @@ -2778,11 +1805,6 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#3c9ee980f1a10d6021ae6632ca3e79ca2ec4fb50" integrity sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA== -"@types/is-function@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83" - integrity sha512-iTs9HReBu7evG77Q4EC8hZnqRt57irBDkK9nvmHroiOIVwYMQc4IvYvdRgwKfYepunIY7Oh/dBuuld+Gj9uo6w== - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -2810,7 +1832,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -2820,40 +1842,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/markdown-to-jsx@^6.11.3": - version "6.11.3" - resolved "https://registry.yarnpkg.com/@types/markdown-to-jsx/-/markdown-to-jsx-6.11.3.tgz#cdd1619308fecbc8be7e6a26f3751260249b020e" - integrity sha512-30nFYpceM/ZEvhGiqWjm5quLUxNeld0HCzJEXMZZDpq53FPkS85mTwkWtCXzCqq8s5JYLgM5W392a02xn8Bdaw== - dependencies: - "@types/react" "*" - -"@types/mdast@^3.0.0": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" - integrity sha512-SXPBMnFVQg1s00dlMCc/jCdvPqdE4mXaMMCeRlxLDmTAEoegHT53xKtkDnzDTOcmMHUfcjyf36/YYZ6SxRdnsw== - dependencies: - "@types/unist" "*" - -"@types/micromatch@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" - integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw== - dependencies: - "@types/braces" "*" - "@types/minimatch@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA== -"@types/node-fetch@^2.5.7": - version "2.5.10" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" - integrity sha512-IpkX0AasN44hgEad0gEF/V6EgR5n69VEqPEgnmoM8GsIGro3PowbWs4tR6IhxUTyPLpOn+fiGG6nrQhcmoCuIQ== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - "@types/node@*": version "15.0.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.3.tgz#ee09fcaac513576474c327da5818d421b98db88a" @@ -2864,51 +1857,21 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.13.tgz#e743bae112bd779ac9650f907197dd2caa7f0364" integrity sha512-1x8W5OpxPq+T85OUsHRP6BqXeosKmeXRtjoF39STcdf/UWLqUsoehstZKOi0CunhVqHG17AyZgpj20eRVooK6A== -"@types/node@^14.0.10", "@types/node@^14.14.35": - version "14.14.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.45.tgz#ec2dfb5566ff814d061aef7e141575aedba245cf" - integrity sha512-DssMqTV9UnnoxDWu959sDLZzfvqCF0qDNRjaWeYSui9xkFe61kKo4l1TWNTQONpuXEm+gLMRvdlzvNHBamzmEw== - "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== -"@types/npmlog@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.2.tgz#d070fe6a6b78755d1092a3dc492d34c3d8f871c4" - integrity sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA== - -"@types/overlayscrollbars@^1.12.0": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@types/overlayscrollbars/-/overlayscrollbars-1.12.0.tgz#98456caceca8ad73bd5bb572632a585074e70764" - integrity sha512-h/pScHNKi4mb+TrJGDon8Yb06ujFG0mSg12wIO0sWMUF3dQIe2ExRRdNRviaNt9IjxIiOfnRr7FsQAdHwK4sMg== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/parse5@^5.0.0": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" - integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== - -"@types/parsimmon@^1.10.1": - version "1.10.6" - resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.6.tgz#8fcf95990514d2a7624aa5f630c13bf2427f9cdd" - integrity sha512-FwAQwMRbkhx0J6YELkwIpciVzCcgEqXEbIrIn3a2P5d3kGEHQ3wVhlN3YdVepYP+bZzCYO6OjmD4o9TGOZ40rA== - "@types/prettier@^2.0.0": version "2.2.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" integrity sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA== -"@types/pretty-hrtime@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.0.tgz#c5a2d644a135e988b2932f99737e67b3c62528d0" - integrity sha512-xl+5r2rcrxdLViAYkkiLMYsoUs3qEyrAnHFyEzYysgRxdVp3WbhysxIvJIxZp9FvZ2CYezh0TaHZorivH+voOQ== - "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -2919,15 +1882,10 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== -"@types/qs@^6.9.5": - version "6.9.6" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" - integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== - -"@types/reach__router@^1.3.7": - version "1.3.7" - resolved "https://registry.yarnpkg.com/@types/reach__router/-/reach__router-1.3.7.tgz#de8ab374259ae7f7499fc1373b9697a5f3cd6428" - integrity sha512-cyBEb8Ef3SJNH5NYEIDGPoMMmYUxROatuxbICusVRQIqZUB85UCt6R2Ok60tKS/TABJsJYaHyNTW3kqbpxlMjg== +"@types/react-autocomplete@^1.8.5": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@types/react-autocomplete/-/react-autocomplete-1.8.6.tgz#999d0cd10ac4164ea605da04d263e5fb3fd5745f" + integrity sha512-v3MOyT7gfMfu0K1Y6n9inGVO325evf96YY3Aw4i4WJs5f9+7Y/TVMPbNXoXRnbhlubgkqel19VnrCdBSodwOEg== dependencies: "@types/react" "*" @@ -2938,6 +1896,13 @@ dependencies: "@types/react" "^16" +"@types/react-form@^2.16.1": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@types/react-form/-/react-form-2.16.4.tgz#e31790e6fe9fb90765e87c3a215f28f04814d171" + integrity sha512-Qj91wZVqqjdeGdMOPlFR91I6E241qnflP0p7nbuX0xJFgy1Rqrae1MtTg3wXGYqOxqF8DQhTTTF9oeVB7Ns8zg== + dependencies: + "@types/react" "*" + "@types/react-helmet@^6.1.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.1.tgz#4fde22cbcaa1b461642e1d719cc6162d95acb110" @@ -2962,13 +1927,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-syntax-highlighter@11.0.5": - version "11.0.5" - resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087" - integrity sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg== - dependencies: - "@types/react" "*" - "@types/react@*", "@types/react@16.9.3", "@types/react@^16", "@types/react@^16.9.3": version "16.9.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.3.tgz#6d13251e441a3e67fb60d719d1fc8785b984a2ec" @@ -3013,16 +1971,6 @@ dependencies: source-map "^0.6.1" -"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" - integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== - -"@types/webpack-env@^1.16.0": - version "1.16.0" - resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4" - integrity sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw== - "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" @@ -3032,7 +1980,7 @@ "@types/source-list-map" "*" source-map "^0.7.3" -"@types/webpack@^4.41.26", "@types/webpack@^4.41.8": +"@types/webpack@^4.41.8": version "4.41.28" resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.28.tgz#0069a2159b7ad4d83d0b5801942c17d54133897b" integrity sha512-Nn84RAiJjKRfPFFCVR8LC4ueTtTdfWAMZ03THIzZWRJB+rX24BD3LqPSFnbMscWauEsT4segAsylPDIaZyZyLQ== @@ -3361,7 +2309,7 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== -acorn-walk@^7.1.1, acorn-walk@^7.2.0: +acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== @@ -3376,7 +2324,7 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0, acorn@^7.4.1: +acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -3407,29 +2355,6 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -airbnb-js-shims@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz#db481102d682b98ed1daa4c5baa697a05ce5c040" - integrity sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ== - dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" - array.prototype.flatmap "^1.2.1" - es5-shim "^4.5.13" - es6-shim "^0.35.5" - function.prototype.name "^1.1.0" - globalthis "^1.0.0" - object.entries "^1.1.0" - object.fromentries "^2.0.0 || ^1.0.0" - object.getownpropertydescriptors "^2.0.3" - object.values "^1.1.0" - promise.allsettled "^1.0.0" - promise.prototype.finally "^3.1.0" - string.prototype.matchall "^4.0.0 || ^3.0.1" - string.prototype.padend "^3.0.0" - string.prototype.padstart "^3.0.0" - symbol.prototype.description "^1.0.0" - ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -3440,7 +2365,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3465,13 +2390,6 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -3499,11 +2417,6 @@ ansi-regex@^2.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -3514,11 +2427,6 @@ ansi-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -3533,13 +2441,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-to-html@^0.6.11: - version "0.6.14" - resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" - integrity sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA== - dependencies: - entities "^1.1.2" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -3556,57 +2457,33 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -app-root-dir@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" - integrity sha1-OBh+wt6nV3//Az/8sSFyaS/24Rg= - -aproba@^1.0.3, aproba@^1.1.1: +aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== +"argo-ui@git+https://github.com/argoproj/argo-ui.git": + version "1.0.0" + resolved "git+https://github.com/argoproj/argo-ui.git#a7be8b3208549c7cf5a0b9531ca41af5614f0eda" dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argo-ux@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/argo-ux/-/argo-ux-1.1.4.tgz#575ec12a02d04c7e8bd3717a3d95344d8127e6a4" - integrity sha512-8oFB4sn434f15wUIwa+1TmJb7+JLAYQa9oAHKKeATdtjUFM9Shbij6nbYGfjN3gWSdiE2WxFOg3AMdIEa34tBw== - dependencies: - "@fortawesome/fontawesome-free" "^5.15.2" - "@fortawesome/fontawesome-svg-core" "^1.2.34" - "@fortawesome/free-regular-svg-icons" "^5.15.2" - "@fortawesome/free-solid-svg-icons" "^5.15.2" - "@fortawesome/react-fontawesome" "^0.1.14" - "@storybook/addon-controls" "^6.2.9" - "@storybook/addon-essentials" "^6.2.9" - "@testing-library/jest-dom" "^5.11.4" - "@testing-library/react" "^11.1.0" - "@testing-library/user-event" "^12.1.10" - "@types/jest" "^26.0.15" - "@types/node" "^12.0.0" - "@types/react" "^16.9.3" - "@types/react-dom" "^16.9.3" + "@fortawesome/fontawesome-free" "^5.8.1" + "@tippy.js/react" "^2.1.2" + "@types/react-autocomplete" "^1.8.5" + "@types/react-form" "^2.16.1" "@types/react-helmet" "^6.1.0" - "@types/react-router-dom" "^5.1.7" - dtslint "^4.0.9" - moment "^2.29.1" - moment-timezone "^0.5.33" - portable-fetch "^3.0.0" + classnames "^2.2.5" + foundation-sites "^6.4.3" + history "^4.7.2" + moment "^2.20.1" + prop-types "^15.6.0" + react-autocomplete "^1.8.1" + react-form "^2.16.0" react-helmet "^6.1.0" - react-hot-loader "^3.1.3" - react-keyhooks "^0.2.2" - react-router-dom "^5.2.0" + react-router-dom "^4.2.2" + react-toastify "^5.0.1" rxjs "^6.6.6" - typescript "^4.1.2" - web-vitals "^1.0.1" - webpack-dev-server "^3.11.2" + typescript "^4.0.3" + xterm "2.4.0" argparse@^1.0.7: version "1.0.10" @@ -3653,7 +2530,7 @@ array-flatten@^2.1.0: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== -array-includes@^3.0.3, array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3: +array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== @@ -3664,7 +2541,7 @@ array-includes@^3.0.3, array-includes@^3.1.1, array-includes@^3.1.2, array-inclu get-intrinsic "^1.1.1" is-string "^1.0.5" -array-union@^1.0.1, array-union@^1.0.2: +array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= @@ -3686,7 +2563,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: +array.prototype.flat@^1.2.3: version "1.2.4" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== @@ -3695,7 +2572,7 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: define-properties "^1.1.3" es-abstract "^1.18.0-next.1" -array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.4: +array.prototype.flatmap@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== @@ -3705,17 +2582,6 @@ array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.4: es-abstract "^1.18.0-next.1" function-bind "^1.1.1" -array.prototype.map@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.3.tgz#1609623618d3d84134a37d4a220030c2bd18420b" - integrity sha512-nNcb30v0wfDyIe26Yif3PcV1JXQp4zEeEfupG7L4SRjnD6HLbO5b2a7eVSba53bOx4YCHYMBHt+Fp4vYstneRA== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - es-array-method-boxes-properly "^1.0.0" - is-string "^1.0.5" - arrify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" @@ -3803,7 +2669,7 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -autoprefixer@^9.6.1, autoprefixer@^9.8.6: +autoprefixer@^9.6.1: version "9.8.6" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== @@ -3836,15 +2702,6 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - babel-eslint@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" @@ -3889,24 +2746,6 @@ babel-loader@8.1.0: pify "^4.0.1" schema-utils "^2.6.5" -babel-loader@^8.2.2: - version "8.2.2" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" - integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== - dependencies: - find-cache-dir "^3.3.1" - loader-utils "^1.4.0" - make-dir "^3.1.0" - schema-utils "^2.6.5" - -babel-plugin-apply-mdx-type-prop@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" - integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - "@mdx-js/util" "1.6.22" - babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" @@ -3914,29 +2753,6 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-emotion@^10.0.27: - version "10.2.2" - resolved "https://registry.yarnpkg.com/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz#a1fe3503cff80abfd0bdda14abd2e8e57a79d17d" - integrity sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA== - dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@emotion/hash" "0.8.0" - "@emotion/memoize" "0.7.4" - "@emotion/serialize" "^0.11.16" - babel-plugin-macros "^2.0.0" - babel-plugin-syntax-jsx "^6.18.0" - convert-source-map "^1.5.0" - escape-string-regexp "^1.0.5" - find-root "^1.1.0" - source-map "^0.5.7" - -babel-plugin-extract-import-names@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" - integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== - dependencies: - "@babel/helper-plugin-utils" "7.10.4" - babel-plugin-istanbul@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" @@ -3958,7 +2774,7 @@ babel-plugin-jest-hoist@^26.6.2: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@2.8.0, babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: +babel-plugin-macros@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -3967,15 +2783,6 @@ babel-plugin-macros@2.8.0, babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8. cosmiconfig "^6.0.0" resolve "^1.12.0" -babel-plugin-macros@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" - integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== - dependencies: - "@babel/runtime" "^7.12.5" - cosmiconfig "^7.0.0" - resolve "^1.19.0" - babel-plugin-named-asset-import@^0.3.7: version "0.3.7" resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz#156cd55d3f1228a5765774340937afc8398067dd" @@ -3990,14 +2797,6 @@ babel-plugin-polyfill-corejs2@^0.2.0: "@babel/helper-define-polyfill-provider" "^0.2.0" semver "^6.1.1" -babel-plugin-polyfill-corejs3@^0.1.0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" - integrity sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.1.5" - core-js-compat "^3.8.1" - babel-plugin-polyfill-corejs3@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz#f4b4bb7b19329827df36ff56f6e6d367026cb7a2" @@ -4013,11 +2812,6 @@ babel-plugin-polyfill-regenerator@^0.2.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.2.0" -babel-plugin-syntax-jsx@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= - babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -4096,17 +2890,12 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== -bail@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" - integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2, base64-js@^1.3.1: +base64-js@^1.0.2: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -4124,11 +2913,6 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -batch-processor@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" - integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg= - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -4141,13 +2925,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -better-opn@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-2.1.1.tgz#94a55b4695dc79288f31d7d0e5f658320759f7c6" - integrity sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA== - dependencies: - open "^7.0.3" - bfj@^7.0.2: version "7.0.2" resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2" @@ -4180,23 +2957,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - -bluebird@^3.3.5, bluebird@^3.5.5: +bluebird@^3.5.5: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -4244,20 +3005,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4412,19 +3159,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= - builtin-modules@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.2.0.tgz#45d5db99e7ee5e6bc4f362e008bf917ab5049887" @@ -4435,11 +3169,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -4517,11 +3246,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -4554,11 +3278,6 @@ camel-case@^4.1.1: pascal-case "^3.1.2" tslib "^2.0.3" -camelcase-css@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -4596,22 +3315,12 @@ case-sensitive-paths-webpack-plugin@2.3.0: resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== -case-sensitive-paths-webpack-plugin@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" - integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -ccount@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" - integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== - -chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4620,17 +3329,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -4652,34 +3350,12 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== -character-entities-legacy@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" - integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== - -character-entities@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" - integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== - -character-reference-invalid@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" - integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== - -charm@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/charm/-/charm-1.0.2.tgz#8add367153a6d9a581331052c4090991da995e35" - integrity sha1-it02cVOm2aWBMxBSxAkJkdqZXjU= - dependencies: - inherits "^2.0.1" - check-types@^11.1.1: version "11.1.2" resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f" integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ== -"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1, chokidar@^3.4.2: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.1: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -4741,6 +3417,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +circular-json@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.4.0.tgz#c448ea998b7fe31ecf472ec29c6b608e2e2a62fd" + integrity sha512-tKV502ADgm9Z37s6B1QOohegjJJrCl2iyMMb1+8ITHrh1fquW8Jdbkb4s5r4Iwutr1UfL1qvkqvc1wZZlLvwow== + cjs-module-lexer@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" @@ -4756,7 +3437,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5: +classnames@^2.2.5, classnames@^2.2.6: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== @@ -4773,30 +3454,6 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-boxes@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-table3@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" - integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== - dependencies: - object-assign "^4.1.0" - string-width "^4.2.0" - optionalDependencies: - colors "^1.1.2" - -clipboard@^2.0.0: - version "2.0.8" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.8.tgz#ffc6c103dd2967a83005f3f61976aa4655a4cdba" - integrity sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ== - dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" - cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -4838,16 +3495,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collapse-white-space@^1.0.2: - version "1.0.6" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" - integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== - collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -4906,29 +3553,14 @@ colorette@^1.2.1, colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -colors@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -comma-separated-tokens@^1.0.0: - version "1.0.8" - resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" - integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== - -command-exists@^1.2.8: - version "1.2.9" - resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" - integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== - -commander@^2.12.1, commander@^2.20.0: +commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -4938,7 +3570,7 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^6.2.0, commander@^6.2.1: +commander@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -4990,17 +3622,12 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -compute-scroll-into-view@^1.0.17: - version "1.0.17" - resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab" - integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5025,11 +3652,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -5052,7 +3674,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: +convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -5091,13 +3713,6 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -copy-to-clipboard@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" - integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== - dependencies: - toggle-selection "^1.0.6" - copy-webpack-plugin@^6.3.2: version "6.4.1" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.4.1.tgz#138cd9b436dbca0a6d071720d5414848992ec47e" @@ -5115,7 +3730,7 @@ copy-webpack-plugin@^6.3.2: serialize-javascript "^5.0.1" webpack-sources "^1.4.3" -core-js-compat@^3.6.2, core-js-compat@^3.8.1, core-js-compat@^3.9.0, core-js-compat@^3.9.1: +core-js-compat@^3.6.2, core-js-compat@^3.9.0, core-js-compat@^3.9.1: version "3.12.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.12.1.tgz#2c302c4708505fa7072b0adb5156d26f7801a18b" integrity sha512-i6h5qODpw6EsHAoIdQhKoZdWn+dGBF3dSS8m5tif36RlWvW3A6+yu2S16QHUo3CrkzrnEskMAt9f8FxmY9fhWQ== @@ -5123,7 +3738,7 @@ core-js-compat@^3.6.2, core-js-compat@^3.8.1, core-js-compat@^3.9.0, core-js-com browserslist "^4.16.6" semver "7.0.0" -core-js-pure@^3.0.0, core-js-pure@^3.8.2: +core-js-pure@^3.0.0: version "3.12.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.12.1.tgz#934da8b9b7221e2a2443dc71dfa5bd77a7ea00b8" integrity sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ== @@ -5133,7 +3748,7 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: +core-js@^3.6.5: version "3.12.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.1.tgz#6b5af4ff55616c08a44d386f1f510917ff204112" integrity sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw== @@ -5175,31 +3790,6 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" -cp-file@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" - integrity sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw== - dependencies: - graceful-fs "^4.1.2" - make-dir "^3.0.0" - nested-error-stacks "^2.0.0" - p-event "^4.1.0" - -cpy@^8.1.1: - version "8.1.2" - resolved "https://registry.yarnpkg.com/cpy/-/cpy-8.1.2.tgz#e339ea54797ad23f8e3919a5cffd37bfc3f25935" - integrity sha512-dmC4mUesv0OYH2kNFEidtf/skUwv4zePmGeepjyyJ0qTo5+8KhA1o99oIAwVVLzQMAeDJml74d6wPPKb6EZUTg== - dependencies: - arrify "^2.0.1" - cp-file "^7.0.0" - globby "^9.2.0" - has-glob "^1.0.0" - junk "^3.1.0" - nested-error-stacks "^2.1.0" - p-all "^2.1.0" - p-filter "^2.1.0" - p-map "^3.0.0" - create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -5231,14 +3821,6 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-context@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" - integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== - dependencies: - gud "^1.0.0" - warning "^4.0.3" - cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5327,25 +3909,6 @@ css-loader@4.3.0: schema-utils "^2.7.1" semver "^7.3.2" -css-loader@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" - integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== - dependencies: - camelcase "^5.3.1" - cssesc "^3.0.0" - icss-utils "^4.1.1" - loader-utils "^1.2.3" - normalize-path "^3.0.0" - postcss "^7.0.32" - postcss-modules-extract-imports "^2.0.0" - postcss-modules-local-by-default "^3.0.2" - postcss-modules-scope "^2.2.0" - postcss-modules-values "^3.0.0" - postcss-value-parser "^4.1.0" - schema-utils "^2.7.0" - semver "^6.3.0" - css-prefers-color-scheme@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" @@ -5520,11 +4083,16 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0, csstype@^2.5.7: +csstype@^2.2.0: version "2.6.17" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e" integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A== +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" @@ -5566,7 +4134,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.1, debug@^3.2.6: +debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -5600,6 +4168,11 @@ dedent@^0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= + deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -5617,11 +4190,6 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deep-object-diff@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" - integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== - deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -5682,16 +4250,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -5710,13 +4268,6 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detab@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" - integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== - dependencies: - repeat-string "^1.5.4" - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -5735,24 +4286,11 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" -detect-port@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1" - integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ== - dependencies: - address "^1.0.1" - debug "^2.6.0" - diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== -diff@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -5762,13 +4300,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5830,6 +4361,19 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dom-scroll-into-view@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dom-scroll-into-view/-/dom-scroll-into-view-1.0.1.tgz#32abb92f0d8feca6215162aef43e4b449ab8d99c" + integrity sha1-Mqu5Lw2P7KYhUWKu9D5LRJq42Zw= + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5895,78 +4439,16 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" -dotenv-defaults@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-1.1.1.tgz#032c024f4b5906d9990eb06d722dc74cc60ec1bd" - integrity sha512-6fPRo9o/3MxKvmRZBD3oNFdxODdhJtIy1zcJeUSCs6HCy4tarUpd+G67UTU9tF6OWXeSPqsm4fPAB+2eY9Rt9Q== - dependencies: - dotenv "^6.2.0" - -dotenv-expand@5.1.0, dotenv-expand@^5.1.0: +dotenv-expand@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-webpack@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-1.8.0.tgz#7ca79cef2497dd4079d43e81e0796bc9d0f68a5e" - integrity sha512-o8pq6NLBehtrqA8Jv8jFQNtG9nhRtVqmoD4yWbgUyoU3+9WBlPe+c2EAiaJok9RB28QvrWvdWLZGeTT5aATDMg== - dependencies: - dotenv-defaults "^1.0.2" - dotenv@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -dotenv@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" - integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== - -dotenv@^8.0.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" - integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== - -downshift@^6.0.15: - version "6.1.3" - resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.3.tgz#e794b7805d24810968f21e81ad6bdd9f3fdc40da" - integrity sha512-RA1MuaNcTbt0j+sVLhSs8R2oZbBXYAtdQP/V+uHhT3DoDteZzJPjlC+LQVm9T07Wpvo84QXaZtUCePLDTDwGXg== - dependencies: - "@babel/runtime" "^7.13.10" - compute-scroll-into-view "^1.0.17" - prop-types "^15.7.2" - react-is "^17.0.2" - -dts-critic@latest: - version "3.3.7" - resolved "https://registry.yarnpkg.com/dts-critic/-/dts-critic-3.3.7.tgz#3f4116f77542e1f11dd25d767f6d07f92a5be989" - integrity sha512-9/bUWvPR4HBdY4p64NZZFVNrB0nMtf4nib33kaNG5UIley+iG6uaMoN9o17iBm+hxhpLtzd4bIt7pb3F53KIMg== - dependencies: - "@definitelytyped/header-parser" latest - command-exists "^1.2.8" - rimraf "^3.0.2" - semver "^6.2.0" - tmp "^0.2.1" - yargs "^15.3.1" - -dtslint@^4.0.9: - version "4.0.9" - resolved "https://registry.yarnpkg.com/dtslint/-/dtslint-4.0.9.tgz#81183764d6a8c5f16cb870f99a0b20dfcd999f04" - integrity sha512-CzDukRUxlmLEYhg0OexdcQNelhEJuoFnulbt0BKlOdvVAypuRp/3GuBbNTyUrHr7rMHI+JUoLCI4o8xv2tyt/w== - dependencies: - "@definitelytyped/header-parser" latest - "@definitelytyped/typescript-versions" latest - "@definitelytyped/utils" latest - dts-critic latest - fs-extra "^6.0.1" - json-stable-stringify "^1.0.1" - strip-json-comments "^2.0.1" - tslint "5.14.0" - tsutils "^2.29.0" - yargs "^15.1.0" - duplexer@^0.1.1, duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -6005,13 +4487,6 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" integrity sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg== -element-resize-detector@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.2.tgz#bf7c3ff915957e4e62e86241ed2f9c86b078892b" - integrity sha512-+LOXRkCJc4I5WhEJxIDjhmE3raF8jtOMBDqSCgZTMz2TX3oXAX5pE2+MDeopJlGdXzP7KzPbBJaUGfNaP9HG4A== - dependencies: - batch-processor "1.0.0" - elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -6030,11 +4505,6 @@ emittery@^0.7.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== -"emoji-regex@>=6.0.0 <=6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" - integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= - emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -6060,15 +4530,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -emotion-theming@^10.0.27: - version "10.0.27" - resolved "https://registry.yarnpkg.com/emotion-theming/-/emotion-theming-10.0.27.tgz#1887baaec15199862c89b1b984b79806f2b9ab10" - integrity sha512-MlF1yu/gYh8u+sLUqA0YuA9JX0P4Hb69WlKc/9OLo+WCXuX6sy/KoIa+qJimgmr2dWqnypYKYPX37esjDBbhdw== - dependencies: - "@babel/runtime" "^7.5.5" - "@emotion/weak-memoize" "0.2.5" - hoist-non-react-statics "^3.3.0" - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6081,14 +4542,14 @@ encoding@^0.1.11: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0, enhanced-resolve@^4.5.0: +enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== @@ -6104,7 +4565,7 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" -entities@^1.1.1, entities@^1.1.2: +entities@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -6147,7 +4608,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.17.0-next.0, es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: +es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2: version "1.18.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== @@ -6169,25 +4630,6 @@ es-abstract@^1.17.0-next.0, es-abstract@^1.17.2, es-abstract@^1.18.0-next.1, es- string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.0" -es-array-method-boxes-properly@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" - integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== - -es-get-iterator@^1.0.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" - integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.0" - has-symbols "^1.0.1" - is-arguments "^1.1.0" - is-map "^2.0.2" - is-set "^2.0.2" - is-string "^1.0.5" - isarray "^2.0.5" - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6206,11 +4648,6 @@ es5-ext@^0.10.35, es5-ext@^0.10.50: es6-symbol "~3.1.3" next-tick "~1.0.0" -es5-shim@^4.5.13: - version "4.5.15" - resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.15.tgz#6a26869b261854a3b045273f5583c52d390217fe" - integrity sha512-FYpuxEjMeDvU4rulKqFdukQyZSTpzhg4ScQHrAosrlVpR6GFyaw14f74yn2+4BugniIS0Frpg7TvwZocU4ZMTw== - es6-iterator@2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -6220,11 +4657,6 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-shim@^0.35.5: - version "0.35.6" - resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0" - integrity sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA== - es6-symbol@^3.1.1, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -6248,7 +4680,7 @@ escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -6676,7 +5108,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -6705,23 +5137,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: +fast-deep-equal@^3.1.1: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^2.2.6: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== - dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" - fast-glob@^3.1.1, fast-glob@^3.2.4: version "3.2.5" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" @@ -6756,13 +5176,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fault@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" - integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== - dependencies: - format "^0.2.0" - faye-websocket@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" @@ -6797,23 +5210,6 @@ file-loader@6.1.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -file-system-cache@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" - integrity sha1-hCWbNqK7uNPW6xAh0xMv/mTP/08= - dependencies: - bluebird "^3.3.5" - fs-extra "^0.30.0" - ramda "^0.21.0" - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -6854,7 +5250,7 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: +find-cache-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== @@ -6872,11 +5268,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-root@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -6899,14 +5290,6 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -6948,7 +5331,7 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.1.6: +fork-ts-checker-webpack-plugin@4.1.6: version "4.1.6" resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz#5055c703febcf37fa06405d400c122b905167fc5" integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== @@ -6961,34 +5344,6 @@ fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.1.6: tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@^6.0.4: - version "6.2.7" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.2.7.tgz#2787fb8e36e0e7d3030f189f9ef84d02f7a07386" - integrity sha512-uGl/x5aiWbBtKT3QtXYuuK/gY1BfN0/RNM/BZvu2o2QdPE7FemyNIxVAgUUbPTm4l90MwWStfVVb7Bu5nyHglg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -6998,16 +5353,16 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -format@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" - integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +foundation-sites@^6.4.3: + version "6.6.3" + resolved "https://registry.yarnpkg.com/foundation-sites/-/foundation-sites-6.6.3.tgz#8ca5f246357db69e6a0e73351ce06aa8acce6540" + integrity sha512-8X93wUAmUg1HhVv8uWMWnwoBLSQWSmFImJencneIZDctswn724Bq/MV1cbPZN/GFWGOB/9ngoQHztfzd4+ovCg== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -7028,31 +5383,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-extra@^0.30.0: - version "0.30.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" - integrity sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - klaw "^1.0.0" - path-is-absolute "^1.0.0" - rimraf "^2.2.8" - -fs-extra@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-6.0.1.tgz#8abc128f7946e310135ddc93b98bddb410e7a34b" - integrity sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -7071,7 +5401,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -7088,11 +5418,6 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" -fs-monkey@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -7121,60 +5446,16 @@ fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.3.1: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83" - integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - functions-have-names "^1.2.2" - functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" - integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== - -fuse.js@^3.6.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.6.1.tgz#7de85fdd6e1b3377c23ce010892656385fd9b10c" - integrity sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw== - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -7235,28 +5516,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -github-slugger@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" - integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== - dependencies: - emoji-regex ">=6.0.0 <=6.1.1" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= - dependencies: - is-glob "^2.0.0" - glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -7272,18 +5531,6 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -glob-promise@^3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" - integrity sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== - dependencies: - "@types/glob" "*" - -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -7312,7 +5559,7 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@^4.3.0, global@^4.4.0: +global@^4.3.0: version "4.4.0" resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== @@ -7339,13 +5586,6 @@ globals@^13.6.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" - integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== - dependencies: - define-properties "^1.1.3" - globby@11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" @@ -7381,28 +5621,7 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" - integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^1.0.2" - dir-glob "^2.2.2" - fast-glob "^2.2.6" - glob "^7.1.3" - ignore "^4.0.3" - pify "^4.0.1" - slash "^2.0.0" - -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= - dependencies: - delegate "^3.1.2" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -7412,11 +5631,6 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== - gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -7455,13 +5669,6 @@ harmony-reflect@^1.4.6: resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.2.tgz#31ecbd32e648a34d030d86adb67d4d47547fe710" integrity sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g== -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -7477,23 +5684,11 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-1.0.0.tgz#9aaa9eedbffb1ba3990a7b0010fb678ee0081207" - integrity sha1-mqqe7b/7G6OZCnsAEPtnjuAIEgc= - dependencies: - is-glob "^3.0.0" - has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -7546,76 +5741,8 @@ hash.js@^1.0.0, hash.js@^1.0.3: resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hast-to-hyperscript@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" - integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== - dependencies: - "@types/unist" "^2.0.3" - comma-separated-tokens "^1.0.0" - property-information "^5.3.0" - space-separated-tokens "^1.0.0" - style-to-object "^0.3.0" - unist-util-is "^4.0.0" - web-namespaces "^1.0.0" - -hast-util-from-parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" - integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== - dependencies: - "@types/parse5" "^5.0.0" - hastscript "^6.0.0" - property-information "^5.0.0" - vfile "^4.0.0" - vfile-location "^3.2.0" - web-namespaces "^1.0.0" - -hast-util-parse-selector@^2.0.0: - version "2.2.5" - resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" - integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== - -hast-util-raw@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" - integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== - dependencies: - "@types/hast" "^2.0.0" - hast-util-from-parse5 "^6.0.0" - hast-util-to-parse5 "^6.0.0" - html-void-elements "^1.0.0" - parse5 "^6.0.0" - unist-util-position "^3.0.0" - vfile "^4.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - -hast-util-to-parse5@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" - integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== - dependencies: - hast-to-hyperscript "^9.0.0" - property-information "^5.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - -hastscript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" - integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== - dependencies: - "@types/hast" "^2.0.0" - comma-separated-tokens "^1.0.0" - hast-util-parse-selector "^2.0.0" - property-information "^5.0.0" - space-separated-tokens "^1.0.0" + inherits "^2.0.3" + minimalistic-assert "^1.0.1" he@^1.2.0: version "1.2.0" @@ -7627,12 +5754,7 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^10.1.1, highlight.js@~10.7.0: - version "10.7.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" - integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== - -history@^4.9.0: +history@^4.7.2, history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== @@ -7653,6 +5775,11 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^2.5.0: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" @@ -7665,7 +5792,7 @@ hoopy@^0.1.4: resolved "https://registry.yarnpkg.com/hoopy/-/hoopy-0.1.4.tgz#609207d661100033a9a9402ad3dea677381c1b1d" integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== -hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: +hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== @@ -7697,7 +5824,7 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" -html-entities@^1.2.0, html-entities@^1.2.1, html-entities@^1.3.1: +html-entities@^1.2.1, html-entities@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA== @@ -7720,16 +5847,6 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" -html-tags@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140" - integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg== - -html-void-elements@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" - integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== - html-webpack-plugin@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz#625097650886b97ea5dae331c320e3238f6c121c" @@ -7745,21 +5862,6 @@ html-webpack-plugin@4.5.0: tapable "^1.1.3" util.promisify "1.0.0" -html-webpack-plugin@^4.0.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" - integrity sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A== - dependencies: - "@types/html-minifier-terser" "^5.0.0" - "@types/tapable" "^1.0.5" - "@types/webpack" "^4.41.8" - html-minifier-terser "^5.0.1" - loader-utils "^1.2.3" - lodash "^4.17.20" - pretty-error "^2.1.1" - tapable "^1.1.3" - util.promisify "1.0.0" - htmlparser2@^3.10.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" @@ -7885,7 +5987,7 @@ identity-obj-proxy@3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.1.4: +ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7895,7 +5997,7 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore@^4.0.3, ignore@^4.0.6: +ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== @@ -7984,7 +6086,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -8004,11 +6106,6 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== - internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -8031,7 +6128,7 @@ interpret@^2.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== -invariant@^2.2.3, invariant@^2.2.4: +invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -8058,7 +6155,7 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-absolute-url@^3.0.0, is-absolute-url@^3.0.3: +is-absolute-url@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== @@ -8077,20 +6174,7 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" -is-alphabetical@1.0.4, is-alphabetical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" - integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== - -is-alphanumerical@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" - integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - -is-arguments@^1.0.4, is-arguments@^1.1.0: +is-arguments@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg== @@ -8138,11 +6222,6 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - is-callable@^1.1.4, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" @@ -8193,11 +6272,6 @@ is-date-object@^1.0.1: resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== -is-decimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" - integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== - is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" @@ -8226,14 +6300,6 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-dom@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.1.0.tgz#af1fced292742443bb59ca3f76ab5e80907b4e8a" - integrity sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ== - dependencies: - is-object "^1.0.1" - is-window "^1.0.2" - is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -8246,23 +6312,11 @@ is-extendable@^1.0.1: dependencies: is-plain-object "^2.0.4" -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= - is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -8273,24 +6327,12 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-function@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" - integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== - is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-glob@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= - dependencies: - is-extglob "^1.0.0" - -is-glob@^3.0.0, is-glob@^3.1.0: +is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= @@ -8304,16 +6346,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-hexadecimal@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" - integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== - -is-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" - integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== - is-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" @@ -8351,11 +6383,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" - integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== - is-path-cwd@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" @@ -8380,16 +6407,6 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-object@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" - integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== - is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -8425,11 +6442,6 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-set@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" - integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== - is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -8457,26 +6469,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-whitespace-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" - integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== - -is-window@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" - integrity sha1-LIlspT25feRdPDMTOmXYyfVjSA0= - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-word-character@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" - integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== - is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" @@ -8499,11 +6496,6 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -8521,11 +6513,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isobject@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" - integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -8572,19 +6559,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterate-iterator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" - integrity sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw== - -iterate-value@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" - integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== - dependencies: - es-get-iterator "^1.0.2" - iterate-iterator "^1.0.1" - jest-changed-files@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" @@ -9002,7 +6976,7 @@ jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^26.2.1, jest-worker@^26.5.0, jest-worker@^26.6.2: +jest-worker@^26.5.0, jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -9020,22 +6994,12 @@ jest@26.6.0: import-local "^3.0.2" jest-cli "^26.6.0" -js-string-escape@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" - integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - -js-yaml@^3.13.1, js-yaml@^3.7.0: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -9120,13 +7084,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -9144,20 +7101,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3: +json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" -jsonfile@^2.1.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -9174,11 +7124,6 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -9197,11 +7142,6 @@ jsprim@^1.2.2: array-includes "^3.1.2" object.assign "^4.1.2" -junk@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" - integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== - killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -9231,13 +7171,6 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -klaw@^1.0.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" - integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= - optionalDependencies: - graceful-fs "^4.1.9" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" @@ -9268,17 +7201,6 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" -lazy-universal-dotenv@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz#a6c8938414bca426ab8c9463940da451a911db38" - integrity sha512-prXSYk799h3GY3iOWnC6ZigYzMPjxN2svgjJ9shk7oMadSNX3wXy0B6F32PMJv7qtMnrIbUxoEHzbutvxR2LBQ== - dependencies: - "@babel/runtime" "^7.5.0" - app-root-dir "^1.0.2" - core-js "^3.0.4" - dotenv "^8.0.0" - dotenv-expand "^5.1.0" - leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -9370,12 +7292,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" +lodash-es@^4.2.1: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash._reinterpolate@^3.0.0: version "3.0.0" @@ -9417,12 +7337,12 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash.uniq@4.5.0, lodash.uniq@^4.5.0: +lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.6.1, lodash@^4.7.0: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.6.1, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9446,14 +7366,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lowlight@^1.14.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" - integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== - dependencies: - fault "^1.0.0" - highlight.js "~10.7.0" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -9480,7 +7392,7 @@ magic-string@^0.25.0, magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" -make-dir@^2.0.0, make-dir@^2.1.0: +make-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== @@ -9488,7 +7400,7 @@ make-dir@^2.0.0, make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -9507,11 +7419,6 @@ map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-or-similar@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" - integrity sha1-beJlMXSt+12e3DPGnT6Sobdvrwg= - map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -9519,24 +7426,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-escapes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" - integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== - -markdown-to-jsx@^6.11.4: - version "6.11.4" - resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz#b4528b1ab668aef7fe61c1535c27e837819392c5" - integrity sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw== - dependencies: - prop-types "^15.6.2" - unquote "^1.1.0" - -markdown-to-jsx@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.2.tgz#19d3da4cd8864045cdd13a0d179147fbd6a088d4" - integrity sha512-O8DMCl32V34RrD+ZHxcAPc2+kYytuDIoQYjY36RVdsLK7uHjgNVvFec4yv0X6LgB4YEZgSvK5QtFi5YVqEpoMA== - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -9546,39 +7435,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -mdast-squeeze-paragraphs@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" - integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== - dependencies: - unist-util-remove "^2.0.0" - -mdast-util-definitions@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" - integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== - dependencies: - unist-util-visit "^2.0.0" - -mdast-util-to-hast@10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" - integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== - dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - mdast-util-definitions "^4.0.0" - mdurl "^1.0.0" - unist-builder "^2.0.0" - unist-util-generated "^1.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - -mdast-util-to-string@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" - integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== - mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -9589,30 +7445,11 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== -mdurl@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^3.1.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.2.2.tgz#5de461389d596e3f23d48bb7c2afb6161f4df40e" - integrity sha512-RE0CwmIM3CEvpcdK3rZ19BC4E6hv9kADkMN5rPduRak58cNArWLi/9jFLsa4rhsjfVxMP3v0jO7FHXq7SvFY5Q== - dependencies: - fs-monkey "1.0.3" - -memoizerific@^1.11.3: - version "1.11.3" - resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" - integrity sha1-fIekZGREwy11Q4VwkF8tvRsagFo= - dependencies: - map-or-similar "^1.5.0" - memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -9639,7 +7476,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3, merge2@^1.3.0: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -9837,7 +7674,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -9856,7 +7693,7 @@ moment-timezone@^0.5.33: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.29.1: +"moment@>= 2.9.0", moment@^2.20.1, moment@^2.29.1: version "2.29.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== @@ -9955,11 +7792,6 @@ neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" - integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== - next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -9986,11 +7818,6 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -10052,7 +7879,7 @@ node-releases@^1.1.61, node-releases@^1.1.71: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== -normalize-package-data@^2.3.2, normalize-package-data@^2.5.0, "normalize-package-data@~1.0.1 || ^2.0.0": +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -10094,35 +7921,6 @@ normalize-url@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0": - version "6.1.1" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.1.tgz#02168cb0a49a2b75bf988a28698de7b529df5cb7" - integrity sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg== - dependencies: - hosted-git-info "^2.7.1" - osenv "^0.1.5" - semver "^5.6.0" - validate-npm-package-name "^3.0.0" - -npm-registry-client@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4" - integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg== - dependencies: - concat-stream "^1.5.2" - graceful-fs "^4.1.6" - normalize-package-data "~1.0.1 || ^2.0.0" - npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" - once "^1.3.3" - request "^2.74.0" - retry "^0.10.0" - safe-buffer "^5.1.1" - semver "2 >=2.2.1 || 3.x || 4 || 5" - slide "^1.1.3" - ssri "^5.2.4" - optionalDependencies: - npmlog "2 || ^3.1.0 || ^4.0.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -10137,16 +7935,6 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -"npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -10159,11 +7947,6 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -10233,7 +8016,7 @@ object.entries@^1.1.0, object.entries@^1.1.3: es-abstract "^1.18.0-next.1" has "^1.0.3" -"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.4: +object.fromentries@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== @@ -10243,7 +8026,7 @@ object.entries@^1.1.0, object.entries@^1.1.3: es-abstract "^1.18.0-next.2" has "^1.0.3" -object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0, object.getownpropertydescriptors@^2.1.2: +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz#1bd63aeacf0d5d2d2f31b5e393b03a7c601a23f7" integrity sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ== @@ -10286,7 +8069,7 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -10300,7 +8083,7 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^7.0.2, open@^7.0.3: +open@^7.0.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== @@ -10364,55 +8147,11 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -overlayscrollbars@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-1.13.1.tgz#0b840a88737f43a946b9d87875a2f9e421d0338a" - integrity sha512-gIQfzgGgu1wy80EB4/6DaJGHMEGmizq27xHIESrzXq0Y/J0Ay1P3DWk6tuVmEPIZH15zaBlxeEJOqdJKmowHCQ== - -p-all@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0" - integrity sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA== - dependencies: - p-map "^2.0.0" - p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== -p-event@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" - integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== - dependencies: - p-timeout "^3.1.0" - -p-filter@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" - integrity sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw== - dependencies: - p-map "^2.0.0" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -10460,25 +8199,11 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-map@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" - integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== - dependencies: - aggregate-error "^3.0.0" - p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -10493,13 +8218,6 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" -p-timeout@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -10550,18 +8268,6 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" -parse-entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" - integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== - dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -10587,7 +8293,7 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse5@6.0.1, parse5@^6.0.0: +parse5@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -10597,11 +8303,6 @@ parseurl@~1.3.2, parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -parsimmon@^1.13.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/parsimmon/-/parsimmon-1.17.0.tgz#520bfaf5d80677cf74d9d2a586b6a503a29223b5" - integrity sha512-gp5yNYs0Lyv5Mp6hj+JMzsHaM4Mel0WuK2iHYKX32ActYAQdsSq+t4nVsqlOpUCiMYdTX1wFISLvugrAl9harg== - pascal-case@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" @@ -10679,13 +8380,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -10717,11 +8411,6 @@ pify@^2.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -10739,7 +8428,7 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= -pirates@^4.0.0, pirates@^4.0.1: +pirates@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== @@ -10767,13 +8456,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-dir@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" - integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== - dependencies: - find-up "^5.0.0" - pkg-up@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" @@ -10788,12 +8470,10 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -polished@^4.0.5: - version "4.1.2" - resolved "https://registry.yarnpkg.com/polished/-/polished-4.1.2.tgz#c04fcc203e287e2d866e9cfcaf102dae1c01a816" - integrity sha512-jq4t3PJUpVRcveC53nnbEX35VyQI05x3tniwp26WFdm1dwaNUBHAi5awa/roBlwQxx1uRhwNSYeAi/aMbfiJCQ== - dependencies: - "@babel/runtime" "^7.13.17" +popper.js@^1.14.7: + version "1.16.1" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" + integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== portable-fetch@^3.0.0: version "3.0.0" @@ -10977,7 +8657,7 @@ postcss-env-function@^2.0.2: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-flexbugs-fixes@4.2.1, postcss-flexbugs-fixes@^4.2.1: +postcss-flexbugs-fixes@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== @@ -11054,17 +8734,6 @@ postcss-loader@3.0.0: postcss-load-config "^2.0.0" schema-utils "^1.0.0" -postcss-loader@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.2.0.tgz#f6993ea3e0f46600fb3ee49bbd010448123a7db4" - integrity sha512-mqgScxHqbiz1yxbnNcPdKYo/6aVt+XExURmEbQlviFVWogDbM4AJ0A/B+ZBpYsJrTRxKw7HyRazg9x0Q9SWwLA== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.4" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - semver "^7.3.4" - postcss-logical@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" @@ -11148,7 +8817,7 @@ postcss-modules-extract-imports@^2.0.0: dependencies: postcss "^7.0.5" -postcss-modules-local-by-default@^3.0.2, postcss-modules-local-by-default@^3.0.3: +postcss-modules-local-by-default@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== @@ -11477,7 +9146,7 @@ postcss@7.0.21: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.35, postcss@^7.0.5, postcss@^7.0.6: +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6: version "7.0.35" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24" integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg== @@ -11510,11 +9179,6 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@~2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" - integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== - pretty-bytes@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -11538,18 +9202,6 @@ pretty-format@^26.0.0, pretty-format@^26.6.0, pretty-format@^26.6.2: ansi-styles "^4.0.0" react-is "^17.0.1" -pretty-hrtime@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= - -prismjs@^1.21.0, prismjs@~1.23.0: - version "1.23.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" - integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== - optionalDependencies: - clipboard "^2.0.0" - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -11570,27 +9222,6 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise.allsettled@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.4.tgz#65e71f2a604082ed69c548b68603294090ee6803" - integrity sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag== - dependencies: - array.prototype.map "^1.0.3" - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - get-intrinsic "^1.0.2" - iterate-value "^1.0.2" - -promise.prototype.finally@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" - integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.0" - function-bind "^1.1.1" - promise@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -11606,7 +9237,7 @@ prompts@2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prompts@^2.0.1, prompts@^2.4.0: +prompts@^2.0.1: version "2.4.1" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== @@ -11614,7 +9245,7 @@ prompts@^2.0.1, prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -11623,13 +9254,6 @@ prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, object-assign "^4.1.1" react-is "^16.8.1" -property-information@^5.0.0, property-information@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" - integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== - dependencies: - xtend "^4.0.0" - proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -11710,13 +9334,6 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.10.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" - integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== - dependencies: - side-channel "^1.0.4" - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -11762,11 +9379,6 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" -ramda@^0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" - integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= - randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -11817,10 +9429,13 @@ react-app-polyfill@^2.0.0: regenerator-runtime "^0.13.7" whatwg-fetch "^3.4.1" -react-colorful@^5.0.1: - version "5.1.4" - resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.1.4.tgz#7391568db7c0a4163436bfb076e5da8ef394e87c" - integrity sha512-WOEpRNz8Oo2SEU4eYQ279jEKFSjpFPa9Vi2U/K0DGwP9wOQ8wYkJcNSd5Qbv1L8OFvyKDCbWekjftXaU5mbmtg== +react-autocomplete@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/react-autocomplete/-/react-autocomplete-1.8.1.tgz#ebbbc400006aa91ad538b2d14727b9e7e5d06310" + integrity sha1-67vEAABqqRrVOLLRRye55+XQYxA= + dependencies: + dom-scroll-into-view "1.0.1" + prop-types "^15.5.10" react-deep-force-update@^2.1.1: version "2.1.3" @@ -11866,42 +9481,29 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.2" -react-draggable@^4.4.3: - version "4.4.3" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.3.tgz#0727f2cae5813e36b0e4962bf11b2f9ef2b406f3" - integrity sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w== - dependencies: - classnames "^2.2.5" - prop-types "^15.6.0" - -react-element-to-jsx-string@^14.3.2: - version "14.3.2" - resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.2.tgz#c0000ed54d1f8b4371731b669613f2d4e0f63d5c" - integrity sha512-WZbvG72cjLXAxV7VOuSzuHEaI3RHj10DZu8EcKQpkKcAj7+qAkG5XUeSdX5FXrA0vPrlx0QsnAzZEBJwzV0e+w== - dependencies: - "@base2/pretty-print-object" "1.0.0" - is-plain-object "3.0.1" - react-error-overlay@^6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== -react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0: +react-fast-compare@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== -react-helmet-async@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/react-helmet-async/-/react-helmet-async-1.0.9.tgz#5b9ed2059de6b4aab47f769532f9fbcbce16c5ca" - integrity sha512-N+iUlo9WR3/u9qGMmP4jiYfaD6pe9IvDTapZLFJz2D3xlTlCM1Bzy4Ab3g72Nbajo/0ZyW+W9hdz8Hbe4l97pQ== +react-form@^2.16.0: + version "2.16.3" + resolved "https://registry.yarnpkg.com/react-form/-/react-form-2.16.3.tgz#c45a575483696ea3f99cf271984fcabcf470f1b2" + integrity sha512-Pp0XFvEUkIWUImEsyi8DQ7j3Ls6h8J7BCXPwIzBOv5ZgOynLFwJX+/gYqQRNtSZbjPP4+3FVA4zW3Qktq7H+lw== dependencies: - "@babel/runtime" "^7.12.5" - invariant "^2.2.4" - prop-types "^15.7.2" - react-fast-compare "^3.2.0" - shallowequal "^1.1.0" + babel-runtime "^6.26.0" + circular-json "^0.4.0" + classnames "^2.2.5" + prop-types "^15.5.10" + react-redux "^5.0.6" + redux "^3.7.2" + redux-logger "^3.0.6" + redux-thunk "^2.2.0" react-helmet@^6.1.0: version "6.1.0" @@ -11924,26 +9526,17 @@ react-hot-loader@^3.1.3: redbox-react "^1.3.6" source-map "^0.6.1" -react-inspector@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8" - integrity sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg== - dependencies: - "@babel/runtime" "^7.0.0" - is-dom "^1.0.0" - prop-types "^15.0.0" - react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1, react-is@^17.0.2: +react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-keyhooks@^0.2.2, react-keyhooks@^0.2.3: +react-keyhooks@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/react-keyhooks/-/react-keyhooks-0.2.3.tgz#57050244d9c501e5b812e2ee3156d8fb967afda9" integrity sha512-1gzHAf5u/JFJ1xUT4Evz9q++eK4px52dhyz8EQ/BXYz4Sb9+idu9IHmq+aGVVoQMFDaO+zLkENoX9sdnVgJ3RQ== @@ -11951,28 +9544,11 @@ react-keyhooks@^0.2.2, react-keyhooks@^0.2.3: "@types/react" "^16.9.3" react "^16.9.3" -react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-popper-tooltip@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-3.1.1.tgz#329569eb7b287008f04fcbddb6370452ad3f9eac" - integrity sha512-EnERAnnKRptQBJyaee5GJScWNUKQPDD2ywvzZyUjst/wj5U64C8/CnSYLNEmP2hG0IJ3ZhtDxE8oDN+KOyavXQ== - dependencies: - "@babel/runtime" "^7.12.5" - "@popperjs/core" "^2.5.4" - react-popper "^2.2.4" - -react-popper@^2.2.4: - version "2.2.5" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96" - integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw== - dependencies: - react-fast-compare "^3.0.1" - warning "^4.0.2" - react-proxy@^3.0.0-alpha.0: version "3.0.0-alpha.1" resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07" @@ -11980,11 +9556,36 @@ react-proxy@^3.0.0-alpha.0: dependencies: lodash "^4.6.1" +react-redux@^5.0.6: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57" + integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q== + dependencies: + "@babel/runtime" "^7.1.2" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + loose-envify "^1.1.0" + prop-types "^15.6.1" + react-is "^16.6.0" + react-lifecycles-compat "^3.0.0" + react-refresh@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== +react-router-dom@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.3.1.tgz#4c2619fc24c4fa87c9fd18f4fb4a43fe63fbd5c6" + integrity sha512-c/MlywfxDdCp7EnB7YfPMOfMD3tOtIjrQlj/CKfNMBxdmpJP8xcz5P/UAFn3JbnQCNUxsHyVVqllF9LhgVyFCA== + dependencies: + history "^4.7.2" + invariant "^2.2.4" + loose-envify "^1.3.1" + prop-types "^15.6.1" + react-router "^4.3.1" + warning "^4.0.1" + react-router-dom@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" @@ -12014,6 +9615,19 @@ react-router@5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-router@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-4.3.1.tgz#aada4aef14c809cb2e686b05cee4742234506c4e" + integrity sha512-yrvL8AogDh2X42Dt9iknk4wF4V8bWREPirFfS9gLU1huk6qK41sg7Z/1S81jjTrGHxa3B8R3J6xIkDAA6CVarg== + dependencies: + history "^4.7.2" + hoist-non-react-statics "^2.5.0" + invariant "^2.2.4" + loose-envify "^1.3.1" + path-to-regexp "^1.7.0" + prop-types "^15.6.1" + warning "^4.0.1" + react-scripts@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.3.tgz#b1cafed7c3fa603e7628ba0f187787964cb5d345" @@ -12085,35 +9699,25 @@ react-side-effect@^2.1.0: resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3" integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ== -react-sizeme@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.1.tgz#4d12f4244e0e6a0fb97253e7af0314dc7c83a5a0" - integrity sha512-9Hf1NLgSbny1bha77l9HwvwwxQUJxFUqi44Ih+y3evA+PezBpGdCGlnvye6avss2cIgs9PgdYgMnfuzJWn/RUw== - dependencies: - element-resize-detector "^1.2.2" - invariant "^2.2.4" - shallowequal "^1.1.0" - throttle-debounce "^3.0.1" - -react-syntax-highlighter@^13.5.3: - version "13.5.3" - resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-13.5.3.tgz#9712850f883a3e19eb858cf93fad7bb357eea9c6" - integrity sha512-crPaF+QGPeHNIblxxCdf2Lg936NAHKhNhuMzRL3F9ct6aYXL3NcZtCL0Rms9+qVo6Y1EQLdXGypBNSbPL/r+qg== +react-toastify@^5.0.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-5.5.0.tgz#f55de44f6b5e3ce3b13b69e5bb4427f2c9404822" + integrity sha512-jsVme7jALIFGRyQsri/g4YTsRuaaGI70T6/ikjwZMB4mwTZaCWqj5NqxhGrRStKlJc5npXKKvKeqTiRGQl78LQ== dependencies: - "@babel/runtime" "^7.3.1" - highlight.js "^10.1.1" - lowlight "^1.14.0" - prismjs "^1.21.0" - refractor "^3.1.0" + "@babel/runtime" "^7.4.2" + classnames "^2.2.6" + prop-types "^15.7.2" + react-transition-group "^4" -react-textarea-autosize@^8.3.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.2.tgz#4f9374d357b0a6f6469956726722549124a1b2db" - integrity sha512-JrMWVgQSaExQByP3ggI1eA8zF4mF0+ddVuX7acUeK2V7bmrpjVOY72vmLz2IXFJSAXoY3D80nEzrn0GWajWK3Q== +react-transition-group@^4: + version "4.4.2" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" + integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg== dependencies: - "@babel/runtime" "^7.10.2" - use-composed-ref "^1.0.0" - use-latest "^1.0.0" + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" react@^16.9.3: version "16.14.0" @@ -12168,7 +9772,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -12181,7 +9785,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -12238,14 +9842,27 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -refractor@^3.1.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.3.1.tgz#ebbc04b427ea81dc25ad333f7f67a0b5f4f0be3a" - integrity sha512-vaN6R56kLMuBszHSWlwTpcZ8KTMG6aUCok4GrxYDT20UIOXxOc5o6oDc8tNTzSlH3m2sI+Eu9Jo2kVdDcUTWYw== +redux-logger@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + +redux-thunk@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A== dependencies: - hastscript "^6.0.0" - parse-entities "^2.0.0" - prismjs "~1.23.0" + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" regenerate-unicode-properties@^8.2.0: version "8.2.0" @@ -12331,74 +9948,6 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -remark-external-links@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/remark-external-links/-/remark-external-links-8.0.0.tgz#308de69482958b5d1cd3692bc9b725ce0240f345" - integrity sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA== - dependencies: - extend "^3.0.0" - is-absolute-url "^3.0.0" - mdast-util-definitions "^4.0.0" - space-separated-tokens "^1.0.0" - unist-util-visit "^2.0.0" - -remark-footnotes@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" - integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== - -remark-mdx@1.6.22: - version "1.6.22" - resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" - integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== - dependencies: - "@babel/core" "7.12.9" - "@babel/helper-plugin-utils" "7.10.4" - "@babel/plugin-proposal-object-rest-spread" "7.12.1" - "@babel/plugin-syntax-jsx" "7.12.1" - "@mdx-js/util" "1.6.22" - is-alphabetical "1.0.4" - remark-parse "8.0.3" - unified "9.2.0" - -remark-parse@8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" - integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== - dependencies: - ccount "^1.0.0" - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^2.0.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^2.0.0" - vfile-location "^3.0.0" - xtend "^4.0.1" - -remark-slug@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-6.0.0.tgz#2b54a14a7b50407a5e462ac2f376022cce263e2c" - integrity sha512-ln67v5BrGKHpETnm6z6adlJPhESFJwfuZZ3jrmi+lKTzeZxh2tzFzUfDD4Pm2hRGOarHLuGToO86MNMZ/hA67Q== - dependencies: - github-slugger "^1.0.0" - mdast-util-to-string "^1.0.0" - unist-util-visit "^2.0.0" - -remark-squeeze-paragraphs@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" - integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== - dependencies: - mdast-squeeze-paragraphs "^4.0.0" - remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -12420,7 +9969,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== -repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -12441,7 +9990,7 @@ request-promise-native@^1.0.9: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.74.0, request@^2.88.2: +request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -12550,7 +10099,7 @@ resolve@1.18.1: is-core-module "^2.0.0" path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0: +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2, resolve@^1.8.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -12571,11 +10120,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -retry@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= - retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -12609,7 +10153,7 @@ rgba-regex@^1.0.0: resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3: +rimraf@^2.5.4, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -12692,11 +10236,6 @@ rxjs@^6.6.6: dependencies: tslib "^1.9.0" -safe-buffer@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -12777,15 +10316,6 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" - integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== - dependencies: - "@types/json-schema" "^7.0.4" - ajv "^6.12.2" - ajv-keywords "^3.4.1" - schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -12795,7 +10325,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0, schema-utils@^2.7.1: +schema-utils@^2.6.5, schema-utils@^2.7.0, schema-utils@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== @@ -12818,11 +10348,6 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= - selfsigned@^1.10.8: version "1.10.11" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.11.tgz#24929cd906fe0f44b6d01fb23999a739537acbe9" @@ -12830,7 +10355,7 @@ selfsigned@^1.10.8: dependencies: node-forge "^0.10.0" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -12845,7 +10370,7 @@ semver@7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -12890,17 +10415,6 @@ serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" -serve-favicon@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" - integrity sha1-k10kDN/g9YBTB/3+ln2IlCosvPA= - dependencies: - etag "~1.8.1" - fresh "0.5.2" - ms "2.1.1" - parseurl "~1.3.2" - safe-buffer "5.1.1" - serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -12924,7 +10438,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -12969,11 +10483,6 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -13043,11 +10552,6 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -slash@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" - integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -13062,11 +10566,6 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -slide@^1.1.3: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -13149,7 +10648,7 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: +source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -13172,7 +10671,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -13194,11 +10693,6 @@ sourcemapped-stacktrace@^1.1.6: dependencies: source-map "0.5.6" -space-separated-tokens@^1.0.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" - integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -13275,13 +10769,6 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssri@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" - integrity sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ== - dependencies: - safe-buffer "^5.1.1" - ssri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" @@ -13318,11 +10805,6 @@ stackframe@^1.1.1: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== -state-toggle@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" - integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== - static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -13341,11 +10823,6 @@ stealthy-require@^1.1.1: resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= -store2@^2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/store2/-/store2-2.12.0.tgz#e1f1b7e1a59b6083b2596a8d067f6ee88fd4d3cf" - integrity sha512-7t+/wpKLanLzSnQPX8WAcuLCCeuSHoWdQuh9SB3xD0kNOM38DNf+0Oa+wmvxmYueRzkmh6IcdKFtvTa+ecgPDw== - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -13396,23 +10873,6 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -13422,7 +10882,7 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== @@ -13431,7 +10891,7 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.4: +string.prototype.matchall@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz#608f255e93e072107f5de066f81a2dfb78cf6b29" integrity sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ== @@ -13444,24 +10904,6 @@ string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0: regexp.prototype.flags "^1.3.1" side-channel "^1.0.4" -string.prototype.padend@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz#6858ca4f35c5268ebd5e8615e1327d55f59ee311" - integrity sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - -string.prototype.padstart@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.1.2.tgz#f9b9ce66bedd7c06acb40ece6e34c6046e1a019d" - integrity sha512-HDpngIP3pd0DeazrfqzuBrQZa+D2arKWquEHfGt5LzVjd+roLC3cjqVI0X8foaZz5rrrhcu8oJAQamW8on9dqw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -13515,13 +10957,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -13564,17 +10999,12 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -style-loader@1.3.0, style-loader@^1.3.0: +style-loader@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== @@ -13582,13 +11012,6 @@ style-loader@1.3.0, style-loader@^1.3.0: loader-utils "^2.0.0" schema-utils "^2.7.0" -style-to-object@0.3.0, style-to-object@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" - integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== - dependencies: - inline-style-parser "0.1.1" - stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" @@ -13598,11 +11021,6 @@ stylehacks@^4.0.0: postcss "^7.0.0" postcss-selector-parser "^3.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -13656,21 +11074,16 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" +symbol-observable@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -symbol.prototype.description@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/symbol.prototype.description/-/symbol.prototype.description-1.0.4.tgz#c30edd3fe8c040d941cf7dc15842be15adf66855" - integrity sha512-fZkHwJ8ZNRVRzF/+/2OtygyyH06CjC0YZAQRHu9jKKw8RXlJpbizEHvGRUu22Qkg182wJk1ugb5Aovcv3UPrww== - dependencies: - call-bind "^1.0.2" - es-abstract "^1.18.0-next.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.2" - table@^6.0.4: version "6.7.0" resolved "https://registry.yarnpkg.com/table/-/table-6.7.0.tgz#26274751f0ee099c547f6cb91d3eff0d61d155b2" @@ -13688,26 +11101,6 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar-stream@^2.1.4: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== - dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" - tar@^6.0.2: version "6.1.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" @@ -13720,20 +11113,6 @@ tar@^6.0.2: mkdirp "^1.0.3" yallist "^4.0.0" -telejson@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/telejson/-/telejson-5.2.0.tgz#c587d0f23c50c9b8749404604e0f9e64589ff180" - integrity sha512-5ALKeIIpOyu0qoKJVsCXufWwh40HrxLasi75xK8KhMDzUHLzD7Cb9epdLjnncE+el0NENIN8sHLaRVOvjbN6ug== - dependencies: - "@types/is-function" "^1.0.0" - global "^4.4.0" - is-function "^1.0.2" - is-regex "^1.1.2" - is-symbol "^1.0.3" - isobject "^4.0.0" - lodash "^4.17.21" - memoizerific "^1.11.3" - temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -13748,11 +11127,6 @@ tempy@^0.3.0: type-fest "^0.3.1" unique-string "^1.0.0" -term-size@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" - integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== - terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -13791,22 +11165,7 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-3.1.0.tgz#91e6d39571460ed240c0cf69d295bcf30ebf98cb" - integrity sha512-cjdZte66fYkZ65rQ2oJfrdCAkkhJA7YLYk5eGOcGCSGlq0ieZupRdjedSQXYknMPo2IveQL+tPdrxUkERENCFA== - dependencies: - cacache "^15.0.5" - find-cache-dir "^3.3.1" - jest-worker "^26.2.1" - p-limit "^3.0.2" - schema-utils "^2.6.6" - serialize-javascript "^4.0.0" - source-map "^0.6.1" - terser "^4.8.0" - webpack-sources "^1.4.3" - -terser@^4.1.2, terser@^4.6.2, terser@^4.6.3, terser@^4.8.0: +terser@^4.1.2, terser@^4.6.2, terser@^4.6.3: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== @@ -13843,11 +11202,6 @@ throat@^5.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== -throttle-debounce@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb" - integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg== - through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -13873,11 +11227,6 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" - integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== - tiny-invariant@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -13888,12 +11237,12 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== +tippy.js@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-4.3.5.tgz#882bff8d92f09bb0546d2826d5668c0560006f54" + integrity sha512-NDq3efte8nGK6BOJ1dDN1/WelAwfmh3UtIYXXck6+SxLzbIQNZE/cmRSnwScZ/FyiKdIcvFHvYUgqmoGx8CcyA== dependencies: - rimraf "^3.0.0" + popper.js "^1.14.7" tmpl@1.0.x: version "1.0.4" @@ -13942,11 +11291,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toggle-selection@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" - integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= - toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" @@ -13981,36 +11325,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" -trim-trailing-lines@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" - integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - integrity sha1-WFhUf2spB1fulczMZm+1AITEYN0= - -trough@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" - integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== - tryer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA== -ts-dedent@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.1.1.tgz#6dd56870bb5493895171334fa5d7e929107e5bbc" - integrity sha512-riHuwnzAUCfdIeTBNUq7+Yj+ANnrMXo/7+Z74dIdudS7ys2k8aSGMzpJRMFDF7CLwUTbtvi1ZZff/Wl+XxmqIA== - -ts-essentials@^2.0.3: - version "2.0.12" - resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" - integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== - ts-loader@^8.0.17: version "8.2.0" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.2.0.tgz#6a3aeaa378aecda543e2ed2c332d3123841d52e0" @@ -14037,7 +11356,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -14047,32 +11366,6 @@ tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== -tslint@5.14.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.14.0.tgz#be62637135ac244fc9b37ed6ea5252c9eba1616e" - integrity sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ== - dependencies: - babel-code-frame "^6.22.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^3.2.0" - glob "^7.1.1" - js-yaml "^3.7.0" - minimatch "^3.0.4" - mkdirp "^0.5.1" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.29.0" - -tsutils@^2.29.0: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" - tsutils@^3.17.1: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -14171,6 +11464,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@^4.0.3: + version "4.3.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" + integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== + typescript@^4.1.2: version "4.2.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" @@ -14186,19 +11484,6 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -unfetch@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" - integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== - -unherit@^1.0.4: - version "1.1.3" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" - integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== - dependencies: - inherits "^2.0.0" - xtend "^4.0.0" - unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -14222,18 +11507,6 @@ unicode-property-aliases-ecmascript@^1.0.4: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== -unified@9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" - integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-buffer "^2.0.0" - is-plain-obj "^2.0.0" - trough "^1.0.0" - vfile "^4.0.0" - union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -14275,64 +11548,6 @@ unique-string@^1.0.0: dependencies: crypto-random-string "^1.0.0" -unist-builder@2.0.3, unist-builder@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" - integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== - -unist-util-generated@^1.0.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" - integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== - -unist-util-is@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" - integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== - -unist-util-position@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" - integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== - -unist-util-remove-position@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" - integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== - dependencies: - unist-util-visit "^2.0.0" - -unist-util-remove@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" - integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== - dependencies: - unist-util-is "^4.0.0" - -unist-util-stringify-position@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" - integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== - dependencies: - "@types/unist" "^2.0.2" - -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - -unist-util-visit@2.0.3, unist-util-visit@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -14348,7 +11563,7 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -unquote@^1.1.0, unquote@~1.1.1: +unquote@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= @@ -14378,7 +11593,7 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url-loader@4.1.1, url-loader@^4.1.1: +url-loader@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== @@ -14403,25 +11618,6 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -use-composed-ref@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" - integrity sha512-my1lNHGWsSDAhhVAT4MKs6IjBUtG6ZG11uUqexPH9PptiIZDQOzaF4f5tEbJ2+7qvNbtXNBbU3SfmN+fXlWDhg== - dependencies: - ts-essentials "^2.0.3" - -use-isomorphic-layout-effect@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225" - integrity sha512-L7Evj8FGcwo/wpbv/qvSfrkHFtOpCzvM5yl2KVyDJoylVuSvzphiiasmjgQPttIGBAy2WKiBNR98q8w7PiNgKQ== - -use-latest@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" - integrity sha512-d2TEuG6nSLKQLAfW3By8mKr8HurOlTkul0sOpxbClIv4SQ4iOd7BYr7VIzdbktUCnv7dua/60xzd8igMU6jmyw== - dependencies: - use-isomorphic-layout-effect "^1.0.0" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -14474,11 +11670,6 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid-browser@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" - integrity sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA= - uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -14511,13 +11702,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validate-npm-package-name@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34= - dependencies: - builtins "^1.0.3" - value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -14542,29 +11726,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vfile-location@^3.0.0, vfile-location@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" - integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== - -vfile-message@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" - integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^2.0.0" - -vfile@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" - integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^2.0.0" - vfile-message "^2.0.0" - vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -14591,7 +11752,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -warning@^4.0.2, warning@^4.0.3: +warning@^4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== @@ -14623,11 +11784,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -web-namespaces@^1.0.0: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" - integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== - web-vitals@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-1.1.2.tgz#06535308168986096239aa84716e68b4c6ae6d1c" @@ -14677,7 +11833,7 @@ webpack-cli@^4.5.0: v8-compile-cache "^2.2.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^3.7.2, webpack-dev-middleware@^3.7.3: +webpack-dev-middleware@^3.7.2: version "3.7.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== @@ -14766,21 +11922,6 @@ webpack-dev-server@^3.11.2: ws "^6.2.1" yargs "^13.3.2" -webpack-filter-warnings-plugin@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz#dc61521cf4f9b4a336fbc89108a75ae1da951cdb" - integrity sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg== - -webpack-hot-middleware@^2.25.0: - version "2.25.0" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.0.tgz#4528a0a63ec37f8f8ef565cf9e534d57d09fe706" - integrity sha512-xs5dPOrGPCzuRXNi8F6rwhawWvQQkeli5Ro48PRuQh8pYPCPmNnltP9itiUPT4xI8oW+y0m59lyyeQk54s5VgA== - dependencies: - ansi-html "0.0.7" - html-entities "^1.2.0" - querystring "^0.2.0" - strip-ansi "^3.0.0" - webpack-log@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" @@ -14815,42 +11956,6 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack- source-list-map "^2.0.0" source-map "~0.6.1" -webpack-virtual-modules@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.2.2.tgz#20863dc3cb6bb2104729fff951fbe14b18bd0299" - integrity sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA== - dependencies: - debug "^3.0.0" - -webpack@4: - version "4.46.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" - integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.5.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - webpack@4.44.2: version "4.44.2" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72" @@ -14950,20 +12055,6 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - wildcard@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" @@ -15196,11 +12287,16 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +xterm@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-2.4.0.tgz#d70227993b74323e36495ab9c7bdee0bc8d0dbba" + integrity sha1-1wInmTt0Mj42SVq5x73uC8jQ27o= + y18n@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" @@ -15253,7 +12349,7 @@ yargs@^13.3.2: y18n "^4.0.0" yargs-parser "^13.1.2" -yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: +yargs@^15.4.1: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== @@ -15274,8 +12370,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zwitch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" - integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== From 17a21582dc274d1bf7e64cc5032b41a618255414 Mon Sep 17 00:00:00 2001 From: cskh Date: Mon, 23 Aug 2021 16:01:08 -0400 Subject: [PATCH 21/34] feat: ability to abort an update when exceeding progressDeadlineSeconds (#1397) Signed-off-by: Hui Kang --- docs/features/specification.md | 5 ++ manifests/crds/rollout-crd.yaml | 2 + manifests/install.yaml | 2 + manifests/namespace-install.yaml | 2 + pkg/apiclient/rollout/rollout.swagger.json | 4 ++ pkg/apis/rollouts/v1alpha1/types.go | 4 ++ rollout/bluegreen_test.go | 61 +++++++++++++++++++ rollout/controller_test.go | 15 +++++ rollout/sync.go | 18 +++++- test/e2e/functional_test.go | 71 ++++++++++++++++++++++ utils/conditions/conditions.go | 4 +- 11 files changed, 185 insertions(+), 3 deletions(-) diff --git a/docs/features/specification.md b/docs/features/specification.md index abf2e5ddee..51a5b354c9 100644 --- a/docs/features/specification.md +++ b/docs/features/specification.md @@ -60,6 +60,11 @@ spec: # Defaults to 600s progressDeadlineSeconds: 600 + # Whether to abort the update when ProgressDeadlineSeconds + # is exceeded if analysis or experiment is not used. + # Optional and default is false. + progressDeadlineAbort: false + # UTC timestamp in which a Rollout should sequentially restart all of # its pods. Used by the `kubectl argo rollouts restart ROLLOUT` command. # The controller will ensure all pods have a creationTimestamp greater diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index cdc7d557ec..f6c8b9b326 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -61,6 +61,8 @@ spec: type: integer paused: type: boolean + progressDeadlineAbort: + type: boolean progressDeadlineSeconds: format: int32 type: integer diff --git a/manifests/install.yaml b/manifests/install.yaml index 1961544028..a0d8bd0fb4 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -9759,6 +9759,8 @@ spec: type: integer paused: type: boolean + progressDeadlineAbort: + type: boolean progressDeadlineSeconds: format: int32 type: integer diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 428dfc0515..02995fa234 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -9759,6 +9759,8 @@ spec: type: integer paused: type: boolean + progressDeadlineAbort: + type: boolean progressDeadlineSeconds: format: int32 type: integer diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 117b49073b..a091dce0c8 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -1185,6 +1185,10 @@ "format": "int32", "description": "ProgressDeadlineSeconds The maximum time in seconds for a rollout to\nmake progress before it is considered to be failed. Argo Rollouts will\ncontinue to process failed rollouts and a condition with a\nProgressDeadlineExceeded reason will be surfaced in the rollout status.\nNote that progress will not be estimated during the time a rollout is paused.\nDefaults to 600s." }, + "progressDeadlineAbort": { + "type": "boolean", + "title": "ProgressDeadlineAbort is whether to abort the update when ProgressDeadlineSeconds\nis exceeded if analysis is not used. Default is false.\n+optional" + }, "restartAt": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.Time", "title": "RestartAt indicates when all the pods of a Rollout should be restarted" diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 193c7855c5..230b9f9d06 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -69,6 +69,10 @@ type RolloutSpec struct { // Note that progress will not be estimated during the time a rollout is paused. // Defaults to 600s. ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,8,opt,name=progressDeadlineSeconds"` + // ProgressDeadlineAbort is whether to abort the update when ProgressDeadlineSeconds + // is exceeded if analysis is not used. Default is false. + // +optional + ProgressDeadlineAbort bool `json:"progressDeadlineAbort,omitempty" protobuf:"varint,12,opt,name=progressDeadlineAbort"` // RestartAt indicates when all the pods of a Rollout should be restarted RestartAt *metav1.Time `json:"restartAt,omitempty" protobuf:"bytes,9,opt,name=restartAt"` // Analysis configuration for the analysis runs to retain diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index 25aa70d46c..a016e8ec89 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -114,6 +114,67 @@ func TestBlueGreenSetPreviewService(t *testing.T) { f.verifyPatchedService(servicePatch, rsPodHash, "") } +// TestBlueGreenProgressDeadlineAbort tests aborting an update if it is timeout +func TestBlueGreenProgressDeadlineAbort(t *testing.T) { + // Two cases to be tested: + // 1. the rollout is making progress, but timeout just happens + // 2. the rollout is not making progress due to timeout and the rollout spec + // is changed to set ProgressDeadlineAbort + tests := []bool{true, false} + + var runRolloutProgressDeadlineAbort func(isTimeout bool) + runRolloutProgressDeadlineAbort = func(isTimeout bool) { + f := newFixture(t) + defer f.Close() + + r := newBlueGreenRollout("foo", 1, nil, "active", "preview") + progressDeadlineSeconds := int32(1) + r.Spec.ProgressDeadlineSeconds = &progressDeadlineSeconds + r.Spec.ProgressDeadlineAbort = true + + f.rolloutLister = append(f.rolloutLister, r) + f.objects = append(f.objects, r) + + rs := newReplicaSetWithStatus(r, 1, 1) + r.Status.UpdatedReplicas = 1 + r.Status.ReadyReplicas = 1 + r.Status.AvailableReplicas = 1 + + rsPodHash := rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + var progressingTimeoutCond *v1alpha1.RolloutCondition + if isTimeout { + msg := fmt.Sprintf("ReplicaSet %q has timed out progressing.", "foo-"+rsPodHash) + progressingTimeoutCond = conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, conditions.TimedOutReason, msg) + } else { + progressingTimeoutCond = conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionTrue, conditions.TimedOutReason, conditions.TimedOutReason) + } + conditions.SetRolloutCondition(&r.Status, *progressingTimeoutCond) + + r.Status.BlueGreen.ActiveSelector = rsPodHash + r.Status.BlueGreen.PreviewSelector = rsPodHash + + previewSvc := newService("preview", 80, nil, r) + selector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rsPodHash} + activeSvc := newService("active", 80, selector, r) + f.kubeobjects = append(f.kubeobjects, previewSvc, activeSvc) + f.serviceLister = append(f.serviceLister, previewSvc, activeSvc) + + f.kubeobjects = append(f.kubeobjects, rs) + f.replicaSetLister = append(f.replicaSetLister, rs) + + f.expectPatchServiceAction(previewSvc, rsPodHash) + patchIndex := f.expectPatchRolloutAction(r) + f.run(getKey(r, t)) + + f.verifyPatchedRolloutAborted(patchIndex, "foo-"+rsPodHash) + } + + for _, tc := range tests { + runRolloutProgressDeadlineAbort(tc) + } +} + //TestSetServiceManagedBy ensures the managed by annotation is set in the service is set func TestSetServiceManagedBy(t *testing.T) { f := newFixture(t) diff --git a/rollout/controller_test.go b/rollout/controller_test.go index d582d4fa38..e4c3cf915a 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -872,6 +872,21 @@ func (f *fixture) verifyPatchedService(index int, newPodHash string, managedBy s assert.Equal(f.t, patch, string(patchAction.GetPatch())) } +func (f *fixture) verifyPatchedRolloutAborted(index int, rsName string) { + action := filterInformerActions(f.kubeclient.Actions())[index] + _, ok := action.(core.PatchAction) + if !ok { + assert.Fail(f.t, "Expected Patch action, not %s", action.GetVerb()) + } + + ro := f.getPatchedRolloutAsObject(index) + assert.NotNil(f.t, ro) + assert.True(f.t, ro.Status.Abort) + assert.Equal(f.t, v1alpha1.RolloutPhaseDegraded, ro.Status.Phase) + expectedMsg := fmt.Sprintf("ProgressDeadlineExceeded: ReplicaSet %q has timed out progressing.", rsName) + assert.Equal(f.t, expectedMsg, ro.Status.Message) +} + func (f *fixture) verifyPatchedAnalysisRun(index int, ar *v1alpha1.AnalysisRun) bool { action := filterInformerActions(f.client.Actions())[index] patchAction, ok := action.(core.PatchAction) diff --git a/rollout/sync.go b/rollout/sync.go index 27f9872195..978e19b8b1 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -648,8 +648,24 @@ func (c *rolloutContext) calculateRolloutConditions(newStatus v1alpha1.RolloutSt if c.newRS != nil { msg = fmt.Sprintf(conditions.ReplicaSetTimeOutMessage, c.newRS.Name) } + condition := conditions.NewRolloutCondition(v1alpha1.RolloutProgressing, corev1.ConditionFalse, conditions.TimedOutReason, msg) - conditions.SetRolloutCondition(&newStatus, *condition) + condChanged := conditions.SetRolloutCondition(&newStatus, *condition) + + // If condition is changed and ProgressDeadlineAbort is set, abort the update + if condChanged { + if c.rollout.Spec.ProgressDeadlineAbort { + c.pauseContext.AddAbort(msg) + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.RolloutAbortedReason}, msg) + } + } else { + // Although condition is unchanged, ProgressDeadlineAbort can be set after + // an existing update timeout. In this case if update is not aborted, we need to abort. + if c.rollout.Spec.ProgressDeadlineAbort && c.pauseContext != nil && !c.pauseContext.IsAborted() { + c.pauseContext.AddAbort(msg) + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.RolloutAbortedReason}, msg) + } + } } } diff --git a/test/e2e/functional_test.go b/test/e2e/functional_test.go index 1c07a73620..5464e870a7 100644 --- a/test/e2e/functional_test.go +++ b/test/e2e/functional_test.go @@ -889,6 +889,77 @@ spec: ExpectReplicaCounts(1, 2, 1, 1, 1) } +// TestBlueGreenAbortExceedProgressDeadline verifies the AbortExceedProgressDeadline feature +func (s *FunctionalSuite) TestBlueGreenExceedProgressDeadlineAbort() { + s.Given(). + RolloutObjects(newService("bluegreen-scaledowndelay-active")). + RolloutObjects(` +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: bluegreen-scaledowndelay +spec: + replicas: 1 + strategy: + blueGreen: + activeService: bluegreen-scaledowndelay-active + abortScaleDownDelaySeconds: 2 + selector: + matchLabels: + app: bluegreen-scaledowndelay + template: + metadata: + labels: + app: bluegreen-scaledowndelay + spec: + containers: + - name: bluegreen-scaledowndelay + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m +`). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + PatchSpec(` +spec: + progressDeadlineAbort: false + progressDeadlineSeconds: 1 + template: + spec: + containers: + - name: bad2good + image: nginx:1.19-alpine-argo-error + command: null`). + WaitForRolloutStatus("Degraded"). + Sleep(3*time.Second). + Then(). + ExpectRevisionPodCount("2", 1). + ExpectRollout("Abort=False", func(r *v1alpha1.Rollout) bool { + return r.Status.Abort == false + }). + When(). + PatchSpec(` +spec: + progressDeadlineAbort: true + progressDeadlineSeconds: 1 + template: + spec: + containers: + - name: bad2good + image: nginx:1.19-alpine-argo-error + command: null`). + WaitForRolloutStatus("Degraded"). + Sleep(3*time.Second). + Then(). + ExpectRevisionPodCount("2", 0). + ExpectRollout("Abort=True", func(r *v1alpha1.Rollout) bool { + return r.Status.Abort == true && len(r.Status.Conditions) == 3 + }) +} + // TestBlueGreenScaleDownOnAbort verifies the scaleDownOnAbort feature func (s *FunctionalSuite) TestBlueGreenScaleDownOnAbort() { s.Given(). diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index 61ae1e016e..0715c19fd0 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -161,8 +161,8 @@ func GetRolloutCondition(status v1alpha1.RolloutStatus, condType v1alpha1.Rollou } // SetRolloutCondition updates the rollout to include the provided condition. If the condition that -// we are about to add already exists and has the same status and reason then we are not going to update. -// Returns true if the condition was updated +// we are about to add already exists and has the same status and reason, then we are not going to update +// by returning false. Returns true if the condition was updated func SetRolloutCondition(status *v1alpha1.RolloutStatus, condition v1alpha1.RolloutCondition) bool { currentCond := GetRolloutCondition(*status, condition.Type) if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason { From 7c6a0c519f226afa9638610837e75bf5adfb09b9 Mon Sep 17 00:00:00 2001 From: harikrongali <81331774+harikrongali@users.noreply.github.com> Date: Mon, 23 Aug 2021 13:12:07 -0700 Subject: [PATCH 22/34] fix: canary scaledown event could violate maxUnavailable (#1429) Signed-off-by: hari rongali --- test/e2e/canary_test.go | 58 +++++++++++++++++++- utils/replicaset/canary.go | 93 +++++++++++++++++++++++++-------- utils/replicaset/canary_test.go | 16 ++++++ 3 files changed, 143 insertions(+), 24 deletions(-) diff --git a/test/e2e/canary_test.go b/test/e2e/canary_test.go index acc0f0d600..07fb2e523d 100644 --- a/test/e2e/canary_test.go +++ b/test/e2e/canary_test.go @@ -112,6 +112,58 @@ func (s *CanarySuite) TestRolloutScalingWhenPaused() { ExpectCanaryStablePodCount(1, 3) } + +// TestRolloutWithMaxSurgeScalingDuringUpdate verifies behavior when scaling a rollout up/down in middle of update and with maxSurge 100% +func (s *CanarySuite) TestRolloutWithMaxSurgeScalingDuringUpdate() { + s.Given(). + HealthyRollout(` +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: updatescaling +spec: + replicas: 4 + strategy: + canary: + maxSurge: 100% + selector: + matchLabels: + app: updatescaling + template: + metadata: + labels: + app: updatescaling + spec: + containers: + - name: updatescaling + image: nginx:1.19-alpine + resources: + requests: + memory: 16Mi + cpu: 1m`). + When(). + PatchSpec(` +spec: + template: + spec: + containers: + - name: updatescaling + command: [/bad-command]`). + WaitForRolloutReplicas(7). + Then(). + ExpectCanaryStablePodCount(4, 3). + When(). + ScaleRollout(8). + WaitForRolloutReplicas(11). + Then(). + ExpectCanaryStablePodCount(8, 3). + When(). + ScaleRollout(4). + WaitForRolloutReplicas(7). + Then(). + ExpectCanaryStablePodCount(4, 3) +} + // TestRolloutScalingDuringUpdate verifies behavior when scaling a rollout up/down in middle of update func (s *CanarySuite) TestRolloutScalingDuringUpdate() { s.Given(). @@ -160,8 +212,10 @@ spec: // See: https://github.com/argoproj/argo-rollouts/issues/738 ExpectCanaryStablePodCount(6, 4). When(). - ScaleRollout(4) - // WaitForRolloutReplicas(4) // this doesn't work yet (bug) + ScaleRollout(4). + WaitForRolloutReplicas(6). + Then(). + ExpectCanaryStablePodCount(2, 4) } // TestReduceWeightAndHonorMaxUnavailable verifies we honor maxUnavailable when decreasing weight or aborting diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go index e03b9bdeab..2068e4b983 100644 --- a/utils/replicaset/canary.go +++ b/utils/replicaset/canary.go @@ -175,12 +175,12 @@ func CalculateReplicaCountsForCanary(rollout *v1alpha1.Rollout, newRS *appsv1.Re } minAvailableReplicaCount := rolloutSpecReplica - MaxUnavailable(rollout) + // isIncreasing indicates if we are supposed to be increasing our canary replica count. // If so, we can ignore pod availability of the stableRS. Otherwise, if we are reducing our // weight (e.g. we are aborting), then we can ignore pod availability of the canaryRS. isIncreasing := newRS == nil || desiredNewRSReplicaCount >= *newRS.Spec.Replicas replicasToScaleDown := GetReplicasForScaleDown(newRS, !isIncreasing) + GetReplicasForScaleDown(stableRS, isIncreasing) - if replicasToScaleDown <= minAvailableReplicaCount { // Cannot scale down stableRS or newRS without going below min available replica count return newRSReplicaCount, stableRSReplicaCount @@ -188,31 +188,80 @@ func CalculateReplicaCountsForCanary(rollout *v1alpha1.Rollout, newRS *appsv1.Re scaleDownCount := replicasToScaleDown - minAvailableReplicaCount - if newRS != nil && *newRS.Spec.Replicas > desiredNewRSReplicaCount { - // if the controller doesn't have to use every replica to achieve the desired count, it only scales down to the - // desired count. - if *newRS.Spec.Replicas-scaleDownCount < desiredNewRSReplicaCount { - newRSReplicaCount = desiredNewRSReplicaCount - // Calculating how many replicas were used to scale down to the desired count - scaleDownCount = scaleDownCount - (*newRS.Spec.Replicas - desiredNewRSReplicaCount) - } else { - // The controller is using every replica it can to get closer to desired state. - newRSReplicaCount = *newRS.Spec.Replicas - scaleDownCount - scaleDownCount = 0 - } + if !isIncreasing { + // Skip scalingDown Stable replicaSet when Canary availability is not taken into calculation for scaleDown + newRSReplicaCount = calculateScaleDownReplicaCount(newRS, desiredNewRSReplicaCount, scaleDownCount, newRSReplicaCount) + newRSReplicaCount, stableRSReplicaCount = adjustReplicaWithinLimits(newRS, stableRS, newRSReplicaCount, stableRSReplicaCount, maxReplicaCountAllowed, minAvailableReplicaCount) + } else { + // Skip scalingDown canary replicaSet when StableSet availability is not taken into calculation for scaleDown + stableRSReplicaCount = calculateScaleDownReplicaCount(stableRS, desiredStableRSReplicaCount, scaleDownCount, stableRSReplicaCount) + stableRSReplicaCount, newRSReplicaCount = adjustReplicaWithinLimits(stableRS, newRS, stableRSReplicaCount, newRSReplicaCount, maxReplicaCountAllowed, minAvailableReplicaCount) } + return newRSReplicaCount, stableRSReplicaCount +} - if scaleStableRS && *stableRS.Spec.Replicas > desiredStableRSReplicaCount { - // This follows the same logic as scaling down the newRS except with the stableRS and it does not need to - // set the scaleDownCount again since it's not used again - if *stableRS.Spec.Replicas-scaleDownCount < desiredStableRSReplicaCount { - stableRSReplicaCount = desiredStableRSReplicaCount - } else { - stableRSReplicaCount = *stableRS.Spec.Replicas - scaleDownCount - } +// calculateScaleDownReplicaCount calculates drainRSReplicaCount +// drainRSReplicaCount can be either stableRS count or canaryRS count +// drainRSReplicaCount corresponds to RS whose availability is not considered in calculating replicasToScaleDown +func calculateScaleDownReplicaCount(drainRS *appsv1.ReplicaSet, desireRSReplicaCount int32, scaleDownCount int32, drainRSReplicaCount int32) int32 { + if drainRS != nil && *drainRS.Spec.Replicas > desireRSReplicaCount { + // if the controller doesn't have to use every replica to achieve the desired count, + // it can scales down to the desired count or get closer to desired state. + drainRSReplicaCount = maxValue(desireRSReplicaCount, *drainRS.Spec.Replicas-scaleDownCount) } + return drainRSReplicaCount +} - return newRSReplicaCount, stableRSReplicaCount +// adjustReplicaWithinLimits adjusts replicaCounters to be within maxSurge & maxUnavailable limits +// drainRSReplicaCount corresponds to RS whose availability is not considered in calculating replicasToScaleDown +// adjustRSReplicaCount corresponds to RS whose availability is to taken account while adjusting maxUnavailable limit +func adjustReplicaWithinLimits(drainRS *appsv1.ReplicaSet, adjustRS *appsv1.ReplicaSet, drainRSReplicaCount int32, adjustRSReplicaCount int32, maxReplicaCountAllowed int32, minAvailableReplicaCount int32) (int32, int32) { + extraAvailableAdjustRS := int32(0) + totalAvailableReplicas := int32(0) + // calculates current limit over the allowed value + overTheLimitVal := maxValue(0, adjustRSReplicaCount+drainRSReplicaCount-maxReplicaCountAllowed) + if drainRS != nil { + totalAvailableReplicas = totalAvailableReplicas + minValue(drainRS.Status.AvailableReplicas, drainRSReplicaCount) + } + if adjustRS != nil { + // 1. adjust adjustRSReplicaCount to be within maxSurge + adjustRSReplicaCount = adjustRSReplicaCount - overTheLimitVal + // 2. Calculate availability corresponding to adjusted count + totalAvailableReplicas = totalAvailableReplicas + minValue(adjustRS.Status.AvailableReplicas, adjustRSReplicaCount) + // 3. Calculate decrease in availability of adjustRS because of (1) + extraAvailableAdjustRS = maxValue(0, adjustRS.Status.AvailableReplicas-adjustRSReplicaCount) + + // 4. Now calculate how far count is from maxUnavailable limit + moreToNeedAvailableReplicas := maxValue(0, minAvailableReplicaCount-totalAvailableReplicas) + // 5. From (3), we got the count for decrease in availability because of (1), + // take the min of (3) & (4) and add it back to adjustRS + // remaining of moreToNeedAvailableReplicas can be ignored as it is part of drainRS, + // there is no case of deviating from maxUnavailable limit from drainRS as in the event of said case, + // scaleDown calculation wont even occur as we are checking + // replicasToScaleDown <= minAvailableReplicaCount in caller function + adjustRSReplicaCount = adjustRSReplicaCount + minValue(extraAvailableAdjustRS, moreToNeedAvailableReplicas) + // 6. Calculate final overTheLimit because of adjustment + overTheLimitVal = maxValue(0, adjustRSReplicaCount+drainRSReplicaCount-maxReplicaCountAllowed) + // 7. we can safely subtract from drainRS and other cases like deviation from maxUnavailable limit + // wont occur as said in (5) + drainRSReplicaCount = drainRSReplicaCount - overTheLimitVal + } + + return drainRSReplicaCount, adjustRSReplicaCount +} + +func minValue(countA int32, countB int32) int32 { + if countA > countB { + return countB + } + return countA +} + +func maxValue(countA int32, countB int32) int32 { + if countA < countB { + return countB + } + return countA } // BeforeStartingStep checks if canary rollout is at the starting step diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go index 65df64f2a6..28e3f3e3c3 100644 --- a/utils/replicaset/canary_test.go +++ b/utils/replicaset/canary_test.go @@ -339,6 +339,22 @@ func TestCalculateReplicaCountsForCanary(t *testing.T) { expectedStableReplicaCount: 7, expectedCanaryReplicaCount: 3, }, + { + name: "Scale down stable and canary available", + rolloutSpecReplicas: 10, + setWeight: 100, + maxSurge: intstr.FromInt(1), + maxUnavailable: intstr.FromInt(0), + + stableSpecReplica: 10, + stableAvailableReplica: 2, + + canarySpecReplica: 10, + canaryAvailableReplica: 8, + + expectedStableReplicaCount: 2, + expectedCanaryReplicaCount: 9, + }, { name: "Do not scale down newRS or stable when older RS count >= scaleDownCount", rolloutSpecReplicas: 10, From 986d4b045ddd2d604bdf8e372dedb40914b37717 Mon Sep 17 00:00:00 2001 From: Kareena Hirani Date: Mon, 23 Aug 2021 14:07:51 -0700 Subject: [PATCH 23/34] feat: TrafficRouting SMI with Experiment Step in Canary (#1351) * feat: Allow Experiment step in TrafficRouting for SMI Signed-off-by: khhirani --- hack/update-mocks.sh | 2 +- manifests/crds/rollout-crd.yaml | 3 + manifests/install.yaml | 3 + manifests/namespace-install.yaml | 3 + pkg/apiclient/rollout/rollout.swagger.json | 5 + pkg/apis/rollouts/v1alpha1/generated.pb.go | 751 +++++++++--------- pkg/apis/rollouts/v1alpha1/generated.proto | 3 + .../rollouts/v1alpha1/openapi_generated.go | 7 + pkg/apis/rollouts/v1alpha1/types.go | 2 + .../v1alpha1/zz_generated.deepcopy.go | 5 + rollout/controller.go | 7 +- rollout/controller_test.go | 3 +- rollout/experiment.go | 3 + rollout/experiment_test.go | 43 + rollout/mocks/TrafficRoutingReconciler.go | 22 +- rollout/trafficrouting.go | 40 +- rollout/trafficrouting/alb/alb.go | 3 +- .../trafficrouting/ambassador/ambassador.go | 3 +- .../ambassador/ambassador_test.go | 7 +- rollout/trafficrouting/istio/controller.go | 3 +- rollout/trafficrouting/istio/istio.go | 3 +- rollout/trafficrouting/nginx/nginx.go | 3 +- rollout/trafficrouting/smi/smi.go | 121 ++- rollout/trafficrouting/smi/smi_test.go | 147 +++- rollout/trafficrouting/trafficroutingutil.go | 20 + rollout/trafficrouting_test.go | 99 ++- test/e2e/crds/split.yaml | 25 + test/e2e/smi/rollout-smi-experiment.yaml | 87 ++ test/e2e/smi_test.go | 104 +++ test/fixtures/common.go | 13 + test/fixtures/e2e_suite.go | 9 + utils/smi/smi.go | 16 + 32 files changed, 1124 insertions(+), 441 deletions(-) create mode 100644 rollout/trafficrouting/trafficroutingutil.go create mode 100644 test/e2e/crds/split.yaml create mode 100644 test/e2e/smi/rollout-smi-experiment.yaml create mode 100644 test/e2e/smi_test.go create mode 100644 utils/smi/smi.go diff --git a/hack/update-mocks.sh b/hack/update-mocks.sh index a24467c103..453d98d7f9 100755 --- a/hack/update-mocks.sh +++ b/hack/update-mocks.sh @@ -18,6 +18,6 @@ mockery \ --output "${PROJECT_ROOT}"/utils/aws/mocks mockery \ - --dir "${PROJECT_ROOT}"/rollout \ + --dir "${PROJECT_ROOT}"/rollout/trafficrouting \ --name TrafficRoutingReconciler \ --output "${PROJECT_ROOT}"/rollout/mocks diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index f6c8b9b326..eb3719983e 100644 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -459,6 +459,9 @@ spec: type: object specRef: type: string + weight: + format: int32 + type: integer required: - name - specRef diff --git a/manifests/install.yaml b/manifests/install.yaml index a0d8bd0fb4..7bd272333c 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -10157,6 +10157,9 @@ spec: type: object specRef: type: string + weight: + format: int32 + type: integer required: - name - specRef diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 02995fa234..eb0e529e79 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -10157,6 +10157,9 @@ spec: type: object specRef: type: string + weight: + format: int32 + type: integer required: - name - specRef diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index a091dce0c8..a66384cec5 100644 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -1128,6 +1128,11 @@ "selector": { "$ref": "#/definitions/k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector", "title": "Selector overrides the selector to be used for the template's ReplicaSet. If omitted, will\nuse the same selector as the Rollout\n+optional" + }, + "weight": { + "type": "integer", + "format": "int32", + "title": "Weight sets the percentage of traffic the template's replicas should receive" } }, "title": "RolloutExperimentTemplate defines the template used to create experiments for the Rollout's experiment canary step" diff --git a/pkg/apis/rollouts/v1alpha1/generated.pb.go b/pkg/apis/rollouts/v1alpha1/generated.pb.go index 471c0d18ed..e2feb8ff37 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.pb.go +++ b/pkg/apis/rollouts/v1alpha1/generated.pb.go @@ -2241,371 +2241,371 @@ func init() { } var fileDescriptor_e0e705f843545fab = []byte{ - // 5816 bytes of a gzipped FileDescriptorProto + // 5823 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5d, 0x6c, 0x24, 0xd9, 0x55, 0xf0, 0x56, 0xff, 0xd8, 0xed, 0xd3, 0xfe, 0xbd, 0xe3, 0xc9, 0xf4, 0xce, 0xee, 0xb8, 0x27, 0xb5, 0xd1, 0x7e, 0x93, 0x8f, 0xc4, 0x4e, 0x66, 0x77, 0x61, 0xc9, 0x46, 0x2b, 0xba, 0xed, 0x99, 0x1d, 0x7b, 0xed, 0x19, 0xcf, 0x6d, 0xcf, 0x8e, 0xb2, 0x9b, 0x0d, 0x29, 0x77, 0x5f, 0xb7, 0x6b, - 0xa6, 0xba, 0xaa, 0x53, 0x55, 0xed, 0x19, 0x6f, 0xa2, 0xfc, 0x10, 0xe5, 0x07, 0x94, 0x28, 0xcb, - 0xcf, 0x0b, 0x42, 0x20, 0x84, 0x78, 0x40, 0xf0, 0x82, 0x50, 0x1e, 0x13, 0x11, 0x01, 0x91, 0x96, - 0x07, 0x50, 0x78, 0x80, 0x0d, 0x48, 0x69, 0x92, 0x0e, 0x12, 0x82, 0x17, 0x14, 0x14, 0x09, 0x65, - 0x25, 0x24, 0x74, 0x7f, 0xea, 0x56, 0xdd, 0xea, 0xea, 0x76, 0xb7, 0xbb, 0x3c, 0x44, 0xc0, 0x9b, - 0xfb, 0x9e, 0x73, 0xcf, 0xb9, 0xb7, 0xee, 0xb9, 0xe7, 0xe7, 0x9e, 0x73, 0xaf, 0x61, 0xbb, 0x69, - 0xfa, 0x87, 0x9d, 0xfd, 0xd5, 0xba, 0xd3, 0x5a, 0x33, 0xdc, 0xa6, 0xd3, 0x76, 0x9d, 0x7b, 0xec, - 0x8f, 0xf7, 0xbb, 0x8e, 0x65, 0x39, 0x1d, 0xdf, 0x5b, 0x6b, 0xdf, 0x6f, 0xae, 0x19, 0x6d, 0xd3, - 0x5b, 0x93, 0x2d, 0x47, 0x1f, 0x34, 0xac, 0xf6, 0xa1, 0xf1, 0xc1, 0xb5, 0x26, 0xb1, 0x89, 0x6b, - 0xf8, 0xa4, 0xb1, 0xda, 0x76, 0x1d, 0xdf, 0x41, 0x1f, 0x0e, 0xa9, 0xad, 0x06, 0xd4, 0xd8, 0x1f, - 0xbf, 0x18, 0xf4, 0x5d, 0x6d, 0xdf, 0x6f, 0xae, 0x52, 0x6a, 0xab, 0xb2, 0x25, 0xa0, 0x76, 0xf1, - 0xfd, 0x91, 0xb1, 0x34, 0x9d, 0xa6, 0xb3, 0xc6, 0x88, 0xee, 0x77, 0x0e, 0xd8, 0x2f, 0xf6, 0x83, - 0xfd, 0xc5, 0x99, 0x5d, 0x7c, 0xea, 0xfe, 0xf3, 0xde, 0xaa, 0xe9, 0xd0, 0xb1, 0xad, 0xed, 0x1b, - 0x7e, 0xfd, 0x70, 0xed, 0xa8, 0x6f, 0x44, 0x17, 0xf5, 0x08, 0x52, 0xdd, 0x71, 0x49, 0x12, 0xce, - 0xb3, 0x21, 0x4e, 0xcb, 0xa8, 0x1f, 0x9a, 0x36, 0x71, 0x8f, 0xc3, 0x59, 0xb7, 0x88, 0x6f, 0x24, - 0xf5, 0x5a, 0x1b, 0xd4, 0xcb, 0xed, 0xd8, 0xbe, 0xd9, 0x22, 0x7d, 0x1d, 0x7e, 0xf6, 0xa4, 0x0e, - 0x5e, 0xfd, 0x90, 0xb4, 0x8c, 0xbe, 0x7e, 0xcf, 0x0c, 0xea, 0xd7, 0xf1, 0x4d, 0x6b, 0xcd, 0xb4, - 0x7d, 0xcf, 0x77, 0xe3, 0x9d, 0xf4, 0x7f, 0xd7, 0x60, 0xa9, 0xb2, 0x5d, 0xdd, 0x73, 0x8d, 0x83, - 0x03, 0xb3, 0x8e, 0x9d, 0x8e, 0x6f, 0xda, 0x4d, 0xf4, 0x5e, 0x98, 0x36, 0xed, 0xa6, 0x4b, 0x3c, - 0xaf, 0xa4, 0x5d, 0xd6, 0xae, 0xcc, 0x54, 0x17, 0xde, 0xea, 0x96, 0x1f, 0xeb, 0x75, 0xcb, 0xd3, - 0x9b, 0xbc, 0x19, 0x07, 0x70, 0xf4, 0x1c, 0x14, 0x3d, 0xe2, 0x1e, 0x99, 0x75, 0xb2, 0xeb, 0xb8, - 0x7e, 0x29, 0x73, 0x59, 0xbb, 0x92, 0xaf, 0x9e, 0x13, 0xe8, 0xc5, 0x5a, 0x08, 0xc2, 0x51, 0x3c, - 0xda, 0xcd, 0x75, 0x1c, 0x5f, 0xc0, 0x4b, 0x59, 0xc6, 0x45, 0x76, 0xc3, 0x21, 0x08, 0x47, 0xf1, - 0xd0, 0x06, 0x2c, 0x1a, 0xb6, 0xed, 0xf8, 0x86, 0x6f, 0x3a, 0xf6, 0xae, 0x4b, 0x0e, 0xcc, 0x87, - 0xa5, 0x1c, 0xeb, 0x5b, 0x12, 0x7d, 0x17, 0x2b, 0x31, 0x38, 0xee, 0xeb, 0xa1, 0x6f, 0x40, 0xa9, - 0xd2, 0xda, 0x37, 0x3c, 0xcf, 0x68, 0x38, 0x6e, 0x6c, 0xea, 0x57, 0xa0, 0xd0, 0x32, 0xda, 0x6d, - 0xd3, 0x6e, 0xd2, 0xb9, 0x67, 0xaf, 0xcc, 0x54, 0x67, 0x7b, 0xdd, 0x72, 0x61, 0x47, 0xb4, 0x61, - 0x09, 0xd5, 0xff, 0x3e, 0x03, 0xc5, 0x8a, 0x6d, 0x58, 0xc7, 0x9e, 0xe9, 0xe1, 0x8e, 0x8d, 0x3e, - 0x0e, 0x05, 0x2a, 0x03, 0x0d, 0xc3, 0x37, 0xd8, 0x57, 0x2b, 0x5e, 0xfd, 0xc0, 0x2a, 0x5f, 0x92, - 0xd5, 0xe8, 0x92, 0x84, 0x92, 0x4d, 0xb1, 0x57, 0x8f, 0x3e, 0xb8, 0x7a, 0x6b, 0xff, 0x1e, 0xa9, - 0xfb, 0x3b, 0xc4, 0x37, 0xaa, 0x48, 0xcc, 0x02, 0xc2, 0x36, 0x2c, 0xa9, 0x22, 0x07, 0x72, 0x5e, - 0x9b, 0xd4, 0xd9, 0x47, 0x2e, 0x5e, 0xdd, 0x59, 0x9d, 0x64, 0x17, 0xad, 0x46, 0x86, 0x5e, 0x6b, - 0x93, 0x7a, 0x75, 0x56, 0xb0, 0xce, 0xd1, 0x5f, 0x98, 0x31, 0x42, 0x0f, 0x60, 0xca, 0xf3, 0x0d, - 0xbf, 0xe3, 0xb1, 0x05, 0x2a, 0x5e, 0xbd, 0x95, 0x1e, 0x4b, 0x46, 0xb6, 0x3a, 0x2f, 0x98, 0x4e, - 0xf1, 0xdf, 0x58, 0xb0, 0xd3, 0xff, 0x41, 0x83, 0x73, 0x11, 0xec, 0x8a, 0xdb, 0xec, 0xb4, 0x88, - 0xed, 0xa3, 0xcb, 0x90, 0xb3, 0x8d, 0x16, 0x11, 0x52, 0x29, 0x87, 0x7c, 0xd3, 0x68, 0x11, 0xcc, - 0x20, 0xe8, 0x29, 0xc8, 0x1f, 0x19, 0x56, 0x87, 0xb0, 0x8f, 0x34, 0x53, 0x9d, 0x13, 0x28, 0xf9, - 0x57, 0x68, 0x23, 0xe6, 0x30, 0xf4, 0x29, 0x98, 0x61, 0x7f, 0x5c, 0x77, 0x9d, 0x56, 0x4a, 0x53, - 0x13, 0x23, 0x7c, 0x25, 0x20, 0x5b, 0x9d, 0xeb, 0x75, 0xcb, 0x33, 0xf2, 0x27, 0x0e, 0x19, 0xea, - 0xff, 0xa8, 0xc1, 0x42, 0x64, 0x72, 0xdb, 0xa6, 0xe7, 0xa3, 0x8f, 0xf6, 0x09, 0xcf, 0xea, 0x68, - 0xc2, 0x43, 0x7b, 0x33, 0xd1, 0x59, 0x14, 0x33, 0x2d, 0x04, 0x2d, 0x11, 0xc1, 0xb1, 0x21, 0x6f, - 0xfa, 0xa4, 0xe5, 0x95, 0x32, 0x97, 0xb3, 0x57, 0x8a, 0x57, 0x37, 0x53, 0x5b, 0xc6, 0xf0, 0xfb, - 0x6e, 0x52, 0xfa, 0x98, 0xb3, 0xd1, 0x7f, 0x3b, 0xa3, 0xcc, 0x90, 0x4a, 0x14, 0x72, 0x60, 0xba, - 0x45, 0x7c, 0xd7, 0xac, 0xf3, 0x7d, 0x55, 0xbc, 0xba, 0x31, 0xd9, 0x28, 0x76, 0x18, 0xb1, 0x50, - 0x33, 0xf1, 0xdf, 0x1e, 0x0e, 0xb8, 0xa0, 0x43, 0xc8, 0x19, 0x6e, 0x33, 0x98, 0xf3, 0xf5, 0x74, - 0xd6, 0x37, 0x94, 0xb9, 0x8a, 0xdb, 0xf4, 0x30, 0xe3, 0x80, 0xd6, 0x60, 0xc6, 0x27, 0x6e, 0xcb, - 0xb4, 0x0d, 0x9f, 0xab, 0xb2, 0x42, 0x75, 0x49, 0xa0, 0xcd, 0xec, 0x05, 0x00, 0x1c, 0xe2, 0xe8, - 0x6f, 0x67, 0x60, 0xa9, 0x6f, 0x33, 0xa0, 0x67, 0x21, 0xdf, 0x3e, 0x34, 0xbc, 0x40, 0xba, 0x57, - 0x82, 0x4f, 0xbb, 0x4b, 0x1b, 0xdf, 0xe9, 0x96, 0xe7, 0x82, 0x2e, 0xac, 0x01, 0x73, 0x64, 0xaa, - 0xab, 0x5b, 0xc4, 0xf3, 0x8c, 0x66, 0x20, 0xf2, 0x91, 0x2f, 0xc2, 0x9a, 0x71, 0x00, 0x47, 0x5f, - 0xd2, 0x60, 0x8e, 0x7f, 0x1d, 0x4c, 0xbc, 0x8e, 0xe5, 0xd3, 0x6d, 0x4d, 0xbf, 0xcd, 0x56, 0x1a, - 0x2b, 0xc1, 0x49, 0x56, 0xcf, 0x0b, 0xee, 0x73, 0xd1, 0x56, 0x0f, 0xab, 0x7c, 0xd1, 0x5d, 0x98, - 0xf1, 0x7c, 0xc3, 0xf5, 0x49, 0xa3, 0xe2, 0x33, 0x05, 0x5e, 0xbc, 0xfa, 0xff, 0x47, 0x93, 0xf7, - 0x3d, 0xb3, 0x45, 0xf8, 0xde, 0xaa, 0x05, 0x04, 0x70, 0x48, 0x4b, 0xff, 0x5b, 0x55, 0x71, 0xd4, - 0x7c, 0x6a, 0xec, 0x9a, 0xc7, 0xe8, 0x35, 0x78, 0xdc, 0xeb, 0xd4, 0xeb, 0xc4, 0xf3, 0x0e, 0x3a, - 0x16, 0xee, 0xd8, 0x37, 0x4c, 0xcf, 0x77, 0xdc, 0xe3, 0x6d, 0xb3, 0x65, 0xfa, 0xec, 0x7b, 0xe7, - 0xab, 0x97, 0x7a, 0xdd, 0xf2, 0xe3, 0xb5, 0x41, 0x48, 0x78, 0x70, 0x7f, 0x64, 0xc0, 0x13, 0x1d, - 0x7b, 0x30, 0x79, 0x6e, 0x13, 0xcb, 0xbd, 0x6e, 0xf9, 0x89, 0x3b, 0x83, 0xd1, 0xf0, 0x30, 0x1a, - 0xfa, 0xbf, 0x6a, 0xb0, 0x18, 0xcc, 0x6b, 0x8f, 0xb4, 0xda, 0x96, 0xe1, 0x93, 0x47, 0x60, 0x71, - 0x7c, 0xc5, 0xe2, 0xe0, 0x74, 0xf4, 0x46, 0x30, 0xfe, 0x41, 0x66, 0x47, 0xff, 0x17, 0x0d, 0x96, - 0xe3, 0xc8, 0x8f, 0x40, 0x4b, 0x7a, 0xaa, 0x96, 0xbc, 0x99, 0xee, 0x6c, 0x07, 0xa8, 0xca, 0x1f, - 0x25, 0xcc, 0xf5, 0x7f, 0xb8, 0xbe, 0xd4, 0xff, 0x20, 0x07, 0xb3, 0x15, 0xdb, 0x37, 0x2b, 0x07, - 0x07, 0xa6, 0x6d, 0xfa, 0xc7, 0xe8, 0x2b, 0x19, 0x58, 0x6b, 0xbb, 0xe4, 0x80, 0xb8, 0x2e, 0x69, - 0x6c, 0x74, 0x5c, 0xd3, 0x6e, 0xd6, 0xea, 0x87, 0xa4, 0xd1, 0xb1, 0x4c, 0xbb, 0xb9, 0xd9, 0xb4, - 0x1d, 0xd9, 0x7c, 0xed, 0x21, 0xa9, 0x77, 0xa8, 0x2b, 0x27, 0xd6, 0xbf, 0x35, 0xd9, 0x30, 0x77, - 0xc7, 0x63, 0x5a, 0x7d, 0xa6, 0xd7, 0x2d, 0xaf, 0x8d, 0xd9, 0x09, 0x8f, 0x3b, 0x35, 0xf4, 0xe5, - 0x0c, 0xac, 0xba, 0xe4, 0x13, 0x1d, 0x73, 0xf4, 0xaf, 0xc1, 0x37, 0xa8, 0x35, 0xd9, 0xd7, 0xc0, - 0x63, 0xf1, 0xac, 0x5e, 0xed, 0x75, 0xcb, 0x63, 0xf6, 0xc1, 0x63, 0xce, 0x4b, 0xff, 0x73, 0x0d, - 0x0a, 0x63, 0x78, 0x7f, 0x65, 0xd5, 0xfb, 0x9b, 0xe9, 0xf3, 0xfc, 0xfc, 0x7e, 0xcf, 0xef, 0xa5, - 0xc9, 0x3e, 0xda, 0x28, 0x1e, 0xdf, 0xbf, 0xd1, 0x28, 0x2b, 0xee, 0x21, 0xa2, 0x43, 0x58, 0x6e, - 0x3b, 0x8d, 0x60, 0xd3, 0xdf, 0x30, 0xbc, 0x43, 0x06, 0x13, 0xd3, 0x7b, 0xb6, 0xd7, 0x2d, 0x2f, - 0xef, 0x26, 0xc0, 0xdf, 0xe9, 0x96, 0x4b, 0x92, 0x48, 0x0c, 0x01, 0x27, 0x52, 0x44, 0x6d, 0x28, - 0x1c, 0x98, 0xc4, 0x6a, 0x60, 0x72, 0x20, 0x24, 0x65, 0xc2, 0xed, 0x7d, 0x5d, 0x50, 0xe3, 0xc1, - 0x51, 0xf0, 0x0b, 0x4b, 0x2e, 0xfa, 0x4f, 0x72, 0xb0, 0x50, 0xb5, 0x3a, 0xe4, 0x25, 0x97, 0x90, - 0xc0, 0xbf, 0xa9, 0xc0, 0x42, 0xdb, 0x25, 0x47, 0x26, 0x79, 0x50, 0x23, 0x16, 0xa9, 0xfb, 0x8e, - 0x2b, 0xa6, 0x7a, 0x41, 0xac, 0xe4, 0xc2, 0xae, 0x0a, 0xc6, 0x71, 0x7c, 0xf4, 0x22, 0xcc, 0x1b, - 0x75, 0xdf, 0x3c, 0x22, 0x92, 0x02, 0x5f, 0xe8, 0x77, 0x09, 0x0a, 0xf3, 0x15, 0x05, 0x8a, 0x63, - 0xd8, 0xe8, 0xa3, 0x50, 0xf2, 0xea, 0x86, 0x45, 0xee, 0xb4, 0x05, 0xab, 0xf5, 0x43, 0x52, 0xbf, - 0xbf, 0xeb, 0x98, 0xb6, 0x2f, 0x1c, 0xb7, 0xcb, 0x82, 0x52, 0xa9, 0x36, 0x00, 0x0f, 0x0f, 0xa4, - 0x80, 0xfe, 0x54, 0x83, 0x4b, 0x6d, 0x97, 0xec, 0xba, 0x4e, 0xcb, 0xa1, 0xd2, 0xdb, 0xe7, 0xe2, - 0x09, 0x57, 0xe7, 0x95, 0x09, 0xb7, 0x29, 0x6f, 0xe9, 0x8f, 0xa6, 0xde, 0xdd, 0xeb, 0x96, 0x2f, - 0xed, 0x0e, 0x1b, 0x00, 0x1e, 0x3e, 0x3e, 0xf4, 0x67, 0x1a, 0xac, 0xb4, 0x1d, 0xcf, 0x1f, 0x32, - 0x85, 0xfc, 0x99, 0x4e, 0x41, 0xef, 0x75, 0xcb, 0x2b, 0xbb, 0x43, 0x47, 0x80, 0x4f, 0x18, 0xa1, - 0xde, 0x2b, 0xc2, 0x52, 0x44, 0xf6, 0x84, 0x07, 0xf8, 0x02, 0xcc, 0x05, 0xc2, 0xc0, 0xcf, 0x1c, - 0xb8, 0xec, 0x49, 0x7f, 0xb5, 0x12, 0x05, 0x62, 0x15, 0x97, 0xca, 0x9d, 0x14, 0x45, 0xde, 0x3b, - 0x26, 0x77, 0xbb, 0x0a, 0x14, 0xc7, 0xb0, 0xd1, 0x26, 0x9c, 0x13, 0x2d, 0x98, 0xb4, 0x2d, 0xb3, - 0x6e, 0xac, 0x3b, 0x1d, 0x21, 0x72, 0xf9, 0xea, 0x85, 0x5e, 0xb7, 0x7c, 0x6e, 0xb7, 0x1f, 0x8c, - 0x93, 0xfa, 0xa0, 0x6d, 0x58, 0x36, 0x3a, 0xbe, 0x23, 0xe7, 0x7f, 0xcd, 0x36, 0xf6, 0x2d, 0xd2, - 0x60, 0xa2, 0x55, 0xa8, 0x96, 0xa8, 0xd6, 0xa8, 0x24, 0xc0, 0x71, 0x62, 0x2f, 0xb4, 0x1b, 0xa3, - 0x56, 0x23, 0x75, 0xc7, 0x6e, 0xf0, 0x55, 0xce, 0x57, 0x9f, 0x14, 0xd3, 0x53, 0x29, 0x0a, 0x1c, - 0x9c, 0xd8, 0x13, 0x59, 0x30, 0xdf, 0x32, 0x1e, 0xde, 0xb1, 0x8d, 0x23, 0xc3, 0xb4, 0x28, 0x93, - 0xd2, 0xd4, 0x09, 0xae, 0x69, 0xc7, 0x37, 0xad, 0x55, 0x7e, 0x3e, 0xb5, 0xba, 0x69, 0xfb, 0xb7, - 0xdc, 0x9a, 0x4f, 0x8d, 0x40, 0x15, 0xd1, 0x0f, 0xbb, 0xa3, 0xd0, 0xc2, 0x31, 0xda, 0xe8, 0x16, - 0x9c, 0x67, 0xdb, 0x71, 0xc3, 0x79, 0x60, 0x6f, 0x10, 0xcb, 0x38, 0x0e, 0x26, 0x30, 0xcd, 0x26, - 0xf0, 0x78, 0xaf, 0x5b, 0x3e, 0x5f, 0x4b, 0x42, 0xc0, 0xc9, 0xfd, 0xa8, 0x2f, 0xaf, 0x02, 0x30, - 0x39, 0x32, 0x3d, 0xd3, 0xb1, 0xb9, 0x2f, 0x5f, 0x08, 0x7d, 0xf9, 0xda, 0x60, 0x34, 0x3c, 0x8c, - 0x06, 0xfa, 0x2d, 0x0d, 0x96, 0x93, 0xb6, 0x61, 0x69, 0x26, 0x8d, 0x73, 0x9d, 0xd8, 0xd6, 0xe2, - 0x12, 0x91, 0xa8, 0x14, 0x12, 0x07, 0x81, 0x3e, 0xab, 0xc1, 0xac, 0x11, 0x71, 0xce, 0x4a, 0xc0, - 0x46, 0xb5, 0x35, 0xa9, 0x37, 0x1c, 0x52, 0xac, 0x2e, 0xf6, 0xba, 0x65, 0xc5, 0x01, 0xc4, 0x0a, - 0x47, 0xf4, 0x3b, 0x1a, 0x9c, 0x4f, 0xdc, 0xe3, 0xa5, 0xe2, 0x59, 0x7c, 0x21, 0x26, 0x24, 0xc9, - 0x3a, 0x27, 0x79, 0x18, 0xe8, 0x4d, 0x4d, 0x9a, 0xb2, 0x9d, 0x20, 0x1e, 0x99, 0x65, 0x43, 0xbb, - 0x3d, 0xa1, 0x3f, 0x1a, 0x5a, 0xef, 0x80, 0x70, 0xf5, 0x5c, 0xc4, 0x32, 0x06, 0x8d, 0x38, 0xce, - 0x1e, 0x7d, 0x55, 0x0b, 0x4c, 0xa3, 0x1c, 0xd1, 0xdc, 0x59, 0x8d, 0x08, 0x85, 0x96, 0x56, 0x0e, - 0x28, 0xc6, 0x1c, 0x7d, 0x0c, 0x2e, 0x1a, 0xfb, 0x8e, 0xeb, 0x27, 0x6e, 0xbe, 0xd2, 0x3c, 0xdb, - 0x46, 0x2b, 0xbd, 0x6e, 0xf9, 0x62, 0x65, 0x20, 0x16, 0x1e, 0x42, 0x41, 0xff, 0xe7, 0x2c, 0xcc, - 0xae, 0x1b, 0xb6, 0xe1, 0x1e, 0x0b, 0xd3, 0xf5, 0x0d, 0x0d, 0x9e, 0xac, 0x77, 0x5c, 0x97, 0xd8, - 0x7e, 0xcd, 0x27, 0xed, 0x7e, 0xc3, 0xa5, 0x9d, 0xa9, 0xe1, 0xba, 0xdc, 0xeb, 0x96, 0x9f, 0x5c, - 0x1f, 0xc2, 0x1f, 0x0f, 0x1d, 0x1d, 0xfa, 0x6b, 0x0d, 0x74, 0x81, 0x50, 0x35, 0xea, 0xf7, 0x9b, - 0xae, 0xd3, 0xb1, 0x1b, 0xfd, 0x93, 0xc8, 0x9c, 0xe9, 0x24, 0x9e, 0xee, 0x75, 0xcb, 0xfa, 0xfa, - 0x89, 0xa3, 0xc0, 0x23, 0x8c, 0x14, 0xbd, 0x04, 0x4b, 0x02, 0xeb, 0xda, 0xc3, 0x36, 0x71, 0x4d, - 0xea, 0xfb, 0x8a, 0x73, 0xfe, 0xc7, 0x85, 0x59, 0x59, 0x5a, 0x8f, 0x23, 0xe0, 0xfe, 0x3e, 0xfa, - 0x1f, 0xe7, 0x00, 0x82, 0x95, 0x26, 0x6d, 0xf4, 0x33, 0x30, 0xe3, 0x11, 0xff, 0x2e, 0x31, 0x9b, - 0x87, 0xc1, 0xc9, 0x0d, 0x3f, 0x0e, 0x0a, 0x1a, 0x71, 0x08, 0x47, 0xf7, 0x21, 0xdf, 0x36, 0x3a, - 0x1e, 0x11, 0xdf, 0x6d, 0x2b, 0x95, 0xef, 0xb6, 0x4b, 0x29, 0xf2, 0xd8, 0x82, 0xfd, 0x89, 0x39, - 0x0f, 0xf4, 0x79, 0x0d, 0x80, 0xa8, 0x73, 0x2d, 0x5e, 0xad, 0xa5, 0xc2, 0x32, 0xfc, 0x1c, 0xf4, - 0x1b, 0x54, 0xe7, 0x7b, 0xdd, 0x32, 0x44, 0xbe, 0x5a, 0x84, 0x2d, 0x7a, 0x00, 0x05, 0x23, 0x50, - 0x97, 0xb9, 0xb3, 0x50, 0x97, 0xcc, 0xe5, 0x97, 0xeb, 0x2d, 0x99, 0xa1, 0x2f, 0x6b, 0x30, 0xef, - 0x11, 0x5f, 0x2c, 0x15, 0xdd, 0xb4, 0xc2, 0x57, 0xdc, 0x9e, 0x8c, 0x7f, 0x4d, 0xa1, 0xc9, 0x95, - 0x8f, 0xda, 0x86, 0x63, 0x7c, 0xf5, 0x37, 0x8b, 0x30, 0x1f, 0x88, 0x4c, 0xe8, 0xfe, 0xd5, 0x79, - 0x4b, 0xb2, 0xfb, 0xb7, 0x1e, 0x05, 0x62, 0x15, 0x97, 0x76, 0xf6, 0x7c, 0xea, 0x6f, 0xa8, 0xde, - 0x9f, 0xec, 0x5c, 0x8b, 0x02, 0xb1, 0x8a, 0x8b, 0x5a, 0x90, 0xf7, 0x7c, 0xd2, 0x0e, 0x0e, 0x5b, - 0x6f, 0x4c, 0xf6, 0x35, 0xc2, 0x9d, 0x10, 0x1e, 0x28, 0xd1, 0x5f, 0x1e, 0xe6, 0x5c, 0xd0, 0xd7, - 0x34, 0x98, 0xf7, 0x95, 0x9c, 0x96, 0x10, 0x83, 0x74, 0x24, 0x51, 0x4d, 0x97, 0xf1, 0xd5, 0x50, - 0xdb, 0x70, 0x8c, 0x7d, 0x82, 0x47, 0x98, 0x3f, 0x43, 0x8f, 0xf0, 0x55, 0x28, 0xb4, 0x8c, 0x87, - 0xb5, 0x8e, 0xdb, 0x3c, 0xbd, 0xe7, 0x29, 0x52, 0x7e, 0x9c, 0x0a, 0x96, 0xf4, 0xd0, 0xe7, 0xb4, - 0xc8, 0xe6, 0x9a, 0x66, 0xc4, 0xef, 0xa6, 0xbb, 0xb9, 0xa4, 0x42, 0x1d, 0xb8, 0xcd, 0xfa, 0xfc, - 0xb3, 0xc2, 0x23, 0xf7, 0xcf, 0xa8, 0xaf, 0xc1, 0x37, 0x88, 0xf4, 0x35, 0x66, 0xce, 0xd4, 0xd7, - 0x58, 0x57, 0x98, 0xe1, 0x18, 0x73, 0x36, 0x1e, 0xbe, 0xe7, 0xe4, 0x78, 0xe0, 0x4c, 0xc7, 0x53, - 0x53, 0x98, 0xe1, 0x18, 0xf3, 0xc1, 0x41, 0x49, 0xf1, 0x6c, 0x82, 0x92, 0xd9, 0x14, 0x82, 0x92, - 0xe1, 0xfe, 0xda, 0xdc, 0xc4, 0xfe, 0xda, 0x8f, 0x34, 0xb8, 0xb0, 0x6e, 0x75, 0x3c, 0x9f, 0xb8, - 0xff, 0x6b, 0xf2, 0x18, 0xff, 0xa1, 0xc1, 0x13, 0x03, 0xe6, 0xfc, 0x08, 0xd2, 0x19, 0x6f, 0xa8, - 0xe9, 0x8c, 0x3b, 0x13, 0xda, 0x9d, 0xe4, 0x79, 0x0c, 0xc8, 0x6a, 0xf8, 0x30, 0xb7, 0x61, 0xf8, - 0x46, 0xc3, 0x69, 0xf2, 0x34, 0x03, 0x7a, 0x11, 0x0a, 0xa6, 0xed, 0x13, 0xf7, 0xc8, 0xb0, 0x84, - 0xe5, 0xd5, 0x83, 0xa1, 0x6f, 0x8a, 0xf6, 0x77, 0xba, 0xe5, 0xf9, 0x8d, 0x8e, 0xcb, 0x0a, 0x35, - 0xb8, 0x1e, 0xc6, 0xb2, 0x0f, 0x7a, 0x0a, 0xf2, 0x9f, 0xe8, 0x10, 0xf7, 0x38, 0x9e, 0xd6, 0xbf, - 0x4d, 0x1b, 0x31, 0x87, 0xe9, 0x7f, 0x97, 0x81, 0x88, 0x57, 0xf4, 0x08, 0xc4, 0xca, 0x56, 0xc4, - 0x6a, 0x42, 0x3f, 0x27, 0xe2, 0xe3, 0x0d, 0xaa, 0xc7, 0x38, 0x8a, 0xd5, 0x63, 0xdc, 0x4c, 0x8d, - 0xe3, 0xf0, 0x72, 0x8c, 0xb7, 0x35, 0x78, 0x22, 0x44, 0xee, 0xf7, 0xf5, 0x4f, 0x3e, 0x98, 0x7f, - 0x0e, 0x8a, 0x46, 0xd8, 0x4d, 0xac, 0xa2, 0xac, 0xf7, 0x89, 0x50, 0xc4, 0x51, 0xbc, 0x30, 0x25, - 0x9e, 0x3d, 0x65, 0x4a, 0x3c, 0x37, 0x3c, 0x25, 0xae, 0xff, 0x38, 0x03, 0x97, 0xfa, 0x67, 0x16, - 0x48, 0x37, 0x26, 0x07, 0x23, 0xcc, 0xed, 0x79, 0x98, 0xf5, 0x45, 0x07, 0xda, 0x2a, 0x26, 0xb7, - 0x2c, 0x30, 0x67, 0xf7, 0x22, 0x30, 0xac, 0x60, 0xd2, 0x9e, 0x75, 0xbe, 0xaf, 0x6a, 0x75, 0xa7, - 0x1d, 0xd4, 0x0e, 0xc8, 0x9e, 0xeb, 0x11, 0x18, 0x56, 0x30, 0x65, 0xb2, 0x2e, 0x77, 0xe6, 0xc5, - 0x0d, 0x35, 0x38, 0x1f, 0xe4, 0x6c, 0xae, 0x3b, 0xee, 0xba, 0xd3, 0x6a, 0x5b, 0x84, 0xa5, 0x9c, - 0xf2, 0x6c, 0xb0, 0x97, 0x44, 0x97, 0xf3, 0x38, 0x09, 0x09, 0x27, 0xf7, 0xd5, 0xdf, 0xce, 0xc2, - 0xb9, 0xf0, 0xb3, 0xaf, 0x3b, 0x76, 0xc3, 0x64, 0x99, 0xaf, 0x17, 0x20, 0xe7, 0x1f, 0xb7, 0x83, - 0x8f, 0xfd, 0xff, 0x82, 0xe1, 0xec, 0x1d, 0xb7, 0xe9, 0x6a, 0x5f, 0x48, 0xe8, 0x42, 0x41, 0x98, - 0x75, 0x42, 0xdb, 0x72, 0x77, 0xf0, 0x15, 0x78, 0x56, 0x95, 0xe6, 0x77, 0xba, 0xe5, 0x84, 0x2a, - 0xbf, 0x55, 0x49, 0x49, 0x95, 0x79, 0x74, 0x0f, 0xe6, 0x2d, 0xc3, 0xf3, 0xef, 0xb4, 0x1b, 0x86, - 0x4f, 0xf6, 0xcc, 0x16, 0x11, 0x7b, 0x6e, 0x9c, 0x3a, 0x05, 0x79, 0x3c, 0xbc, 0xad, 0x50, 0xc2, - 0x31, 0xca, 0xe8, 0x08, 0x10, 0x6d, 0xd9, 0x73, 0x0d, 0xdb, 0xe3, 0xb3, 0xa2, 0xfc, 0xc6, 0xaf, - 0x8b, 0xb8, 0x28, 0xf8, 0xa1, 0xed, 0x3e, 0x6a, 0x38, 0x81, 0x03, 0x7a, 0x1a, 0xa6, 0x5c, 0x62, - 0x78, 0x62, 0x31, 0x67, 0xc2, 0xfd, 0x8f, 0x59, 0x2b, 0x16, 0xd0, 0xe8, 0x86, 0x9a, 0x3a, 0x61, - 0x43, 0x7d, 0x4f, 0x83, 0xf9, 0x70, 0x99, 0x1e, 0x81, 0x99, 0x6b, 0xa9, 0x66, 0xee, 0x46, 0x5a, - 0x2a, 0x71, 0x80, 0x65, 0xfb, 0x93, 0x5c, 0x74, 0x7e, 0x2c, 0x53, 0xff, 0x49, 0x98, 0x09, 0x76, - 0x75, 0x90, 0xab, 0x9f, 0xd0, 0x1b, 0x57, 0x3c, 0x8b, 0x48, 0x29, 0x91, 0x60, 0x82, 0x43, 0x7e, - 0xd4, 0xb0, 0x36, 0x84, 0xd1, 0x14, 0x62, 0x2f, 0x0d, 0x6b, 0x60, 0x4c, 0x93, 0x0c, 0x6b, 0xd0, - 0x07, 0xdd, 0x81, 0x0b, 0x6d, 0xd7, 0x61, 0xb5, 0x9c, 0x1b, 0xc4, 0x68, 0x58, 0xa6, 0x4d, 0x02, - 0xa7, 0x8f, 0x67, 0x27, 0x9e, 0xe8, 0x75, 0xcb, 0x17, 0x76, 0x93, 0x51, 0xf0, 0xa0, 0xbe, 0x6a, - 0x49, 0x54, 0xee, 0xe4, 0x92, 0x28, 0xf4, 0xcb, 0x32, 0xb4, 0x22, 0x5e, 0x29, 0xcf, 0x3e, 0xe2, - 0x6b, 0x69, 0x2d, 0x65, 0x82, 0x5a, 0x0f, 0x45, 0xaa, 0x22, 0x98, 0x62, 0xc9, 0x7e, 0xb0, 0xff, - 0x3e, 0x75, 0x3a, 0xff, 0x5d, 0xff, 0x42, 0x1e, 0x16, 0xe3, 0xc6, 0xf6, 0xec, 0xcb, 0xbd, 0x7e, - 0x4d, 0x83, 0xc5, 0x40, 0x50, 0x38, 0x4f, 0x12, 0x1c, 0x42, 0x6c, 0xa7, 0x24, 0x9f, 0xdc, 0x6d, - 0x90, 0xb5, 0xb7, 0x7b, 0x31, 0x6e, 0xb8, 0x8f, 0x3f, 0x7a, 0x1d, 0x8a, 0x32, 0x56, 0x3f, 0x55, - 0xed, 0xd7, 0x02, 0x73, 0x18, 0x42, 0x12, 0x38, 0x4a, 0x0f, 0x7d, 0x41, 0x03, 0xa8, 0x07, 0x1a, - 0x3d, 0x10, 0xa4, 0xdb, 0x69, 0x09, 0x92, 0xb4, 0x15, 0xa1, 0x5f, 0x28, 0x9b, 0x3c, 0x1c, 0x61, - 0x8c, 0x7e, 0x9d, 0x45, 0xe9, 0xd2, 0x91, 0xa1, 0xa2, 0x43, 0x47, 0xf2, 0x91, 0xb4, 0x45, 0x3a, - 0x3c, 0xbb, 0x95, 0x5e, 0x43, 0x04, 0xe4, 0x61, 0x65, 0x10, 0xfa, 0x0b, 0x20, 0x73, 0xf5, 0x74, - 0x87, 0xb2, 0x6c, 0xfd, 0xae, 0xe1, 0x1f, 0x0a, 0x11, 0x94, 0x3b, 0xf4, 0x7a, 0x00, 0xc0, 0x21, - 0x8e, 0xfe, 0x17, 0x1a, 0x2c, 0x6f, 0x7a, 0xbe, 0xe9, 0x6c, 0x10, 0xcf, 0xa7, 0x9b, 0x96, 0xda, - 0xf7, 0x8e, 0x45, 0x46, 0xf0, 0x90, 0x36, 0x60, 0x51, 0x1c, 0xa8, 0x75, 0xf6, 0x3d, 0xe2, 0x47, - 0xbc, 0x24, 0x29, 0x3a, 0xeb, 0x31, 0x38, 0xee, 0xeb, 0x41, 0xa9, 0x88, 0x93, 0xb5, 0x90, 0x4a, - 0x56, 0xa5, 0x52, 0x8b, 0xc1, 0x71, 0x5f, 0x0f, 0xfd, 0x9b, 0x19, 0x38, 0xc7, 0xa6, 0x11, 0x2b, - 0xfc, 0xfe, 0x55, 0x0d, 0xe6, 0x8f, 0x4c, 0xd7, 0xef, 0x18, 0x56, 0xf4, 0x88, 0x70, 0x62, 0xe9, - 0x61, 0xbc, 0x5e, 0x51, 0x08, 0x87, 0x7e, 0x81, 0xda, 0x8e, 0x63, 0x03, 0xa0, 0x63, 0x5a, 0x68, - 0xa8, 0x5f, 0x3b, 0x9d, 0x10, 0x36, 0x69, 0x1d, 0x79, 0xa2, 0x29, 0xd6, 0x88, 0xe3, 0xfc, 0xf5, - 0xd7, 0xc4, 0xe7, 0x53, 0x87, 0x3e, 0x82, 0x10, 0xe8, 0x30, 0xe5, 0x3a, 0x1d, 0x6a, 0x23, 0x33, - 0xac, 0xae, 0x1e, 0x98, 0xa3, 0xc1, 0x5a, 0xb0, 0x80, 0xe8, 0x7f, 0xa4, 0xc1, 0xcc, 0x96, 0xb3, - 0x2f, 0x82, 0xc6, 0x8f, 0xa5, 0x10, 0xc0, 0x49, 0x3d, 0x2f, 0x4f, 0x6b, 0x42, 0xd7, 0xe1, 0x45, - 0x25, 0x7c, 0x7b, 0x32, 0x42, 0x7b, 0x95, 0x5d, 0x14, 0xa1, 0xa4, 0xb6, 0x9c, 0xfd, 0x81, 0xf1, - 0xfd, 0xef, 0xe5, 0x61, 0xee, 0x65, 0xe3, 0x98, 0xd8, 0xbe, 0x21, 0x46, 0xfc, 0x5e, 0x98, 0x36, - 0x1a, 0x8d, 0xa4, 0x8b, 0x13, 0x15, 0xde, 0x8c, 0x03, 0x38, 0x8b, 0x88, 0xda, 0x2c, 0xaf, 0x1f, - 0xb1, 0xdd, 0x61, 0x44, 0x14, 0x82, 0x70, 0x14, 0x2f, 0xdc, 0x4a, 0xeb, 0x8e, 0x7d, 0x60, 0x36, - 0x93, 0x36, 0xc1, 0x7a, 0x0c, 0x8e, 0xfb, 0x7a, 0xa0, 0x2d, 0x40, 0xa2, 0xec, 0xaf, 0x52, 0xaf, - 0x3b, 0x1d, 0x9b, 0x6f, 0x26, 0x1e, 0x2c, 0x49, 0x27, 0x72, 0xa7, 0x0f, 0x03, 0x27, 0xf4, 0x42, - 0x1f, 0x85, 0x52, 0x9d, 0x51, 0x16, 0x2e, 0x45, 0x94, 0x22, 0x77, 0x2b, 0x65, 0x4d, 0xcd, 0xfa, - 0x00, 0x3c, 0x3c, 0x90, 0x02, 0x1d, 0xa9, 0xe7, 0x3b, 0xae, 0xd1, 0x24, 0x51, 0xba, 0x53, 0xea, - 0x48, 0x6b, 0x7d, 0x18, 0x38, 0xa1, 0x17, 0xfa, 0x0c, 0xcc, 0xf8, 0x87, 0x2e, 0xf1, 0x0e, 0x1d, - 0xab, 0x21, 0x8e, 0x6f, 0x27, 0x8c, 0xa0, 0xc5, 0xea, 0xef, 0x05, 0x54, 0x23, 0x4e, 0x4e, 0xd0, - 0x84, 0x43, 0x9e, 0xc8, 0x85, 0x29, 0x8f, 0x86, 0x6f, 0x5e, 0xa9, 0x90, 0x86, 0x9b, 0x28, 0xb8, - 0xb3, 0x88, 0x30, 0x12, 0xbb, 0x33, 0x0e, 0x58, 0x70, 0xd2, 0xbf, 0x9d, 0x81, 0xd9, 0x28, 0xe2, - 0x08, 0x3b, 0xf5, 0xf3, 0x1a, 0xcc, 0xd6, 0x1d, 0xdb, 0x77, 0x1d, 0x8b, 0xc7, 0xa5, 0x7c, 0x83, - 0x4c, 0x78, 0x6d, 0x80, 0x91, 0xda, 0x20, 0xbe, 0x61, 0x5a, 0x91, 0x10, 0x37, 0xc2, 0x06, 0x2b, - 0x4c, 0xd1, 0x57, 0x34, 0x58, 0x08, 0xf3, 0x5a, 0x61, 0x80, 0x9c, 0xea, 0x40, 0x64, 0xe9, 0xd9, - 0x35, 0x95, 0x13, 0x8e, 0xb3, 0xd6, 0xf7, 0x61, 0x31, 0xbe, 0xda, 0xf4, 0x53, 0xb6, 0x0d, 0xb1, - 0xd7, 0xb3, 0xe1, 0xa7, 0xdc, 0x35, 0x3c, 0x0f, 0x33, 0x08, 0x7a, 0x1f, 0x14, 0x5a, 0x86, 0xdb, - 0x34, 0x6d, 0xc3, 0x62, 0x5f, 0x31, 0x1b, 0x51, 0x48, 0xa2, 0x1d, 0x4b, 0x0c, 0xfd, 0x87, 0x39, - 0x28, 0xee, 0x10, 0xc3, 0xeb, 0xb8, 0x84, 0x9d, 0x60, 0x9d, 0xb9, 0x8b, 0xa8, 0xd4, 0xe1, 0x67, - 0xd3, 0xab, 0xc3, 0x47, 0xaf, 0x02, 0x1c, 0x98, 0xb6, 0xe9, 0x1d, 0x9e, 0xb2, 0xc2, 0x9f, 0x65, - 0x38, 0xaf, 0x4b, 0x0a, 0x38, 0x42, 0x2d, 0xbc, 0xe2, 0x93, 0x1f, 0x72, 0xc5, 0xe7, 0x0b, 0x5a, - 0xc4, 0x78, 0x70, 0xe7, 0xeb, 0xee, 0xa4, 0x05, 0xd4, 0x72, 0x61, 0x56, 0x03, 0x63, 0x72, 0xcd, - 0xf6, 0xdd, 0xe3, 0xa1, 0x36, 0x66, 0x0f, 0x0a, 0x2e, 0xf1, 0x3a, 0x2d, 0xea, 0xec, 0x4e, 0x8f, - 0xfd, 0x19, 0x58, 0x12, 0x08, 0x8b, 0xfe, 0x58, 0x52, 0xba, 0xf8, 0x02, 0xcc, 0x29, 0x43, 0x40, - 0x8b, 0x90, 0xbd, 0x4f, 0x8e, 0xb9, 0x9c, 0x60, 0xfa, 0x27, 0x5a, 0x56, 0x4a, 0x61, 0xc5, 0x67, - 0xf9, 0x50, 0xe6, 0x79, 0x4d, 0xff, 0xf1, 0x14, 0x4c, 0x09, 0x7b, 0x75, 0xb2, 0x2e, 0x88, 0x1e, - 0xdc, 0x66, 0x4e, 0x71, 0x70, 0xbb, 0x05, 0xb3, 0xa6, 0x6d, 0xfa, 0xa6, 0x61, 0xb1, 0x88, 0x48, - 0xd8, 0xaa, 0xa7, 0x83, 0xfd, 0xbf, 0x19, 0x81, 0x25, 0xd0, 0x51, 0xfa, 0xa2, 0xdb, 0x90, 0x67, - 0xca, 0x5c, 0xc8, 0xd3, 0xf8, 0x79, 0x3d, 0x96, 0xb3, 0xe7, 0xb5, 0x75, 0x9c, 0x12, 0xf3, 0x29, - 0xf9, 0xa5, 0x0b, 0xe9, 0xc8, 0x0b, 0xb1, 0x0a, 0x7d, 0xca, 0x18, 0x1c, 0xf7, 0xf5, 0xa0, 0x54, - 0x0e, 0x0c, 0xd3, 0xea, 0xb8, 0x24, 0xa4, 0x32, 0xa5, 0x52, 0xb9, 0x1e, 0x83, 0xe3, 0xbe, 0x1e, - 0xe8, 0x00, 0x66, 0x45, 0x1b, 0x4f, 0xeb, 0x4c, 0x9f, 0x72, 0x96, 0x2c, 0x7d, 0x77, 0x3d, 0x42, - 0x09, 0x2b, 0x74, 0x51, 0x07, 0x96, 0x4c, 0xbb, 0xee, 0xd8, 0x75, 0xab, 0xe3, 0x99, 0x47, 0x24, - 0x2c, 0x6c, 0x3b, 0x0d, 0xb3, 0xf3, 0xbd, 0x6e, 0x79, 0x69, 0x33, 0x4e, 0x0e, 0xf7, 0x73, 0x40, - 0x9f, 0xd3, 0xe0, 0x7c, 0xdd, 0xb1, 0x3d, 0x56, 0xda, 0x7d, 0x44, 0xae, 0xb9, 0xae, 0xe3, 0x72, - 0xde, 0x33, 0xa7, 0xe4, 0xcd, 0x02, 0xf1, 0xf5, 0x24, 0x92, 0x38, 0x99, 0x13, 0x7a, 0x03, 0x0a, - 0x6d, 0xd7, 0x39, 0x32, 0x1b, 0xc4, 0x15, 0x29, 0xc2, 0xed, 0x34, 0x6e, 0x55, 0xec, 0x0a, 0x9a, - 0xa1, 0x26, 0x08, 0x5a, 0xb0, 0xe4, 0xa7, 0x7f, 0x7d, 0x0a, 0xe6, 0x55, 0x74, 0xf4, 0x69, 0x80, - 0xb6, 0xeb, 0xb4, 0x88, 0x7f, 0x48, 0x64, 0x81, 0xd2, 0xcd, 0x49, 0x6f, 0x34, 0x04, 0xf4, 0xc4, - 0x85, 0x0f, 0xa6, 0x49, 0xc3, 0x56, 0x1c, 0xe1, 0x88, 0x5c, 0x98, 0xbe, 0xcf, 0x6d, 0x9a, 0x30, - 0xf1, 0x2f, 0xa7, 0xe2, 0x90, 0x08, 0xce, 0x45, 0x6a, 0x72, 0x44, 0x13, 0x0e, 0x18, 0xa1, 0x7d, - 0xc8, 0x3e, 0x20, 0xfb, 0xe9, 0xd4, 0xde, 0xdf, 0x25, 0x22, 0x54, 0xa8, 0x4e, 0xf7, 0xba, 0xe5, - 0xec, 0x5d, 0xb2, 0x8f, 0x29, 0x71, 0x3a, 0xaf, 0x06, 0x4f, 0x3f, 0x09, 0x55, 0x31, 0xe1, 0xbc, - 0x94, 0x5c, 0x16, 0x9f, 0x97, 0x68, 0xc2, 0x01, 0x23, 0xf4, 0x06, 0xcc, 0x3c, 0x30, 0x8e, 0xc8, - 0x81, 0xeb, 0xd8, 0xbe, 0x28, 0x70, 0x98, 0xb0, 0xf0, 0xe6, 0x6e, 0x40, 0x4e, 0xf0, 0x65, 0xd6, - 0x56, 0x36, 0xe2, 0x90, 0x1d, 0x3a, 0x82, 0x82, 0x4d, 0x1e, 0x60, 0x62, 0x99, 0x75, 0x51, 0xf3, - 0x30, 0xa1, 0x58, 0xdf, 0x14, 0xd4, 0x04, 0x67, 0x66, 0x86, 0x82, 0x36, 0x2c, 0x79, 0xd1, 0xb5, - 0xbc, 0xe7, 0xec, 0x0b, 0x45, 0x35, 0xe1, 0x5a, 0xca, 0xb0, 0x8f, 0xaf, 0xe5, 0x96, 0xb3, 0x8f, - 0x29, 0x71, 0xfd, 0x9b, 0x39, 0x98, 0x8d, 0xde, 0x25, 0x1c, 0xc1, 0x66, 0x49, 0xb7, 0x29, 0x33, - 0x8e, 0xdb, 0x44, 0xbd, 0xde, 0x56, 0x68, 0xe3, 0x83, 0xa3, 0xb2, 0xcd, 0xd4, 0xbc, 0x86, 0xd0, - 0xeb, 0x8d, 0x34, 0x7a, 0x58, 0x61, 0x3a, 0x46, 0xee, 0x8a, 0xfa, 0x41, 0xdc, 0x1c, 0xf2, 0x62, - 0x6d, 0xe9, 0x07, 0x29, 0x06, 0xee, 0x2a, 0x40, 0x78, 0xab, 0x50, 0x1c, 0x60, 0xca, 0xc3, 0xab, - 0xc8, 0x6d, 0xc7, 0x08, 0x16, 0x7a, 0x1a, 0xa6, 0xa8, 0xc1, 0x20, 0x0d, 0x51, 0x45, 0x2d, 0x43, - 0x8b, 0xeb, 0xac, 0x15, 0x0b, 0x28, 0x7a, 0x9e, 0xda, 0xf6, 0x50, 0xcd, 0x8b, 0xe2, 0xe8, 0xe5, - 0xd0, 0xb6, 0x87, 0x30, 0xac, 0x60, 0xd2, 0xa1, 0x13, 0xaa, 0x95, 0x99, 0xea, 0x8f, 0x0c, 0x9d, - 0xa9, 0x6a, 0xcc, 0x61, 0x2c, 0xd4, 0x8d, 0x69, 0x71, 0xa6, 0xb4, 0xf3, 0x91, 0x50, 0x37, 0x06, - 0xc7, 0x7d, 0x3d, 0xf4, 0x8f, 0xc3, 0xbc, 0x2a, 0xcd, 0xf4, 0x13, 0xb7, 0x5d, 0xe7, 0xc0, 0xb4, - 0x48, 0x3c, 0x48, 0xdf, 0xe5, 0xcd, 0x38, 0x80, 0x8f, 0x96, 0x76, 0xfe, 0xcb, 0x2c, 0x9c, 0xbb, - 0xd9, 0x34, 0xed, 0x87, 0xb1, 0x13, 0xa5, 0xa4, 0xc7, 0x0a, 0xb4, 0x71, 0x1f, 0x2b, 0x08, 0x6b, - 0xcf, 0xc4, 0xd3, 0x0b, 0xc9, 0xb5, 0x67, 0xc1, 0xbb, 0x0c, 0x2a, 0x2e, 0xfa, 0x9e, 0x06, 0x4f, - 0x1a, 0x0d, 0xee, 0x5f, 0x18, 0x96, 0x68, 0x0d, 0x99, 0x06, 0x32, 0xee, 0x4d, 0xa8, 0x2d, 0xfa, - 0x27, 0xbf, 0x5a, 0x19, 0xc2, 0x95, 0x7b, 0xcd, 0xef, 0x11, 0x33, 0x78, 0x72, 0x18, 0x2a, 0x1e, - 0x3a, 0xfc, 0x8b, 0xb7, 0xe0, 0xdd, 0x27, 0x32, 0x1a, 0xcb, 0x37, 0xfe, 0xbc, 0x06, 0x33, 0xfc, - 0xf4, 0x08, 0x93, 0x03, 0xba, 0x79, 0x8c, 0xb6, 0xf9, 0x0a, 0x71, 0xbd, 0xe0, 0xc6, 0xe1, 0x4c, - 0xb8, 0x79, 0x2a, 0xbb, 0x9b, 0x02, 0x82, 0x23, 0x58, 0x54, 0x3d, 0xdd, 0x37, 0xed, 0x86, 0x58, - 0x26, 0xa9, 0x9e, 0x5e, 0x36, 0xed, 0x06, 0x66, 0x10, 0xa9, 0xc0, 0xb2, 0x83, 0x14, 0x98, 0xfe, - 0xfb, 0x1a, 0xcc, 0xb3, 0xd2, 0xd2, 0xd0, 0x39, 0x7c, 0x4e, 0xa6, 0xea, 0xf8, 0x30, 0x2e, 0xa9, - 0xa9, 0xba, 0x77, 0xba, 0xe5, 0x22, 0x2f, 0x46, 0x55, 0x33, 0x77, 0xaf, 0x89, 0x00, 0x8f, 0x25, - 0x14, 0x33, 0x63, 0xc7, 0x1f, 0xf2, 0x38, 0xa3, 0x16, 0x10, 0xc1, 0x21, 0x3d, 0xfd, 0xeb, 0x59, - 0x38, 0x97, 0x50, 0x23, 0x45, 0x63, 0xaf, 0x29, 0xcb, 0xd8, 0x27, 0x56, 0x90, 0x0e, 0x7b, 0x3d, - 0xf5, 0x3a, 0xac, 0xd5, 0x6d, 0x46, 0x9f, 0x4b, 0x92, 0xd4, 0x4f, 0xbc, 0x11, 0x0b, 0xe6, 0xe8, - 0x37, 0x35, 0x28, 0x1a, 0x11, 0x61, 0xe7, 0x19, 0xc2, 0xfd, 0xf4, 0x07, 0xd3, 0x27, 0xdb, 0x91, - 0xca, 0x86, 0x50, 0x94, 0xa3, 0x63, 0xb9, 0xf8, 0xf3, 0x50, 0x8c, 0x4c, 0x61, 0x1c, 0x19, 0xbd, - 0xf8, 0x22, 0x2c, 0x4e, 0x24, 0xe3, 0x1f, 0x81, 0x71, 0xaf, 0xb0, 0x52, 0x8b, 0xf0, 0x20, 0x5a, - 0x71, 0x2d, 0xbf, 0xb8, 0x28, 0xb9, 0x16, 0x50, 0x7d, 0x1f, 0x16, 0xe3, 0x0e, 0xe8, 0x38, 0x67, - 0xa2, 0x23, 0xa9, 0xdb, 0x0f, 0xc0, 0x98, 0x97, 0x4e, 0xf5, 0xbf, 0xca, 0xc0, 0xb4, 0x28, 0xb4, - 0x7c, 0x04, 0x45, 0x41, 0xf7, 0x95, 0x53, 0xe5, 0xcd, 0x54, 0xea, 0x43, 0x07, 0x56, 0x04, 0x79, - 0xb1, 0x8a, 0xa0, 0x97, 0xd3, 0x61, 0x37, 0xbc, 0x1c, 0xe8, 0x6b, 0x19, 0x58, 0x88, 0x15, 0xae, - 0xa2, 0x2f, 0x6a, 0xfd, 0x59, 0xf0, 0x3b, 0xa9, 0xd6, 0xc6, 0xca, 0x92, 0xb3, 0xe1, 0x09, 0x71, - 0x4f, 0xb9, 0xc6, 0x7e, 0x3b, 0xb5, 0xa7, 0x4e, 0x86, 0xde, 0x68, 0xff, 0x27, 0x0d, 0x1e, 0x1f, - 0x58, 0xca, 0xcb, 0xae, 0x0b, 0xb9, 0x2a, 0x54, 0xc8, 0x5e, 0xca, 0xa5, 0xf9, 0xf2, 0x34, 0x33, - 0x7e, 0xc3, 0x23, 0xce, 0x1e, 0x3d, 0x0b, 0xb3, 0x4c, 0x8f, 0xd3, 0xed, 0xe3, 0x93, 0xb6, 0x78, - 0xa3, 0x82, 0x9d, 0x1c, 0xd4, 0x22, 0xed, 0x58, 0xc1, 0xd2, 0x7f, 0x57, 0x83, 0xd2, 0xa0, 0xcb, - 0x23, 0x23, 0xf8, 0xe5, 0x3f, 0x17, 0x2b, 0xd0, 0x29, 0xf7, 0x15, 0xe8, 0xc4, 0x3c, 0xf3, 0xa0, - 0x16, 0x27, 0xe2, 0x14, 0x67, 0x4f, 0xa8, 0x3f, 0xf9, 0xaa, 0x06, 0x17, 0x06, 0x08, 0x4e, 0x5f, - 0xa1, 0x96, 0x76, 0xea, 0x42, 0xad, 0xcc, 0xa8, 0x85, 0x5a, 0xfa, 0xdf, 0x64, 0x61, 0x51, 0x8c, - 0x27, 0x34, 0xe6, 0xcf, 0x2b, 0x65, 0x4e, 0xef, 0x89, 0x95, 0x39, 0x2d, 0xc7, 0xf1, 0xff, 0xaf, - 0xc6, 0xe9, 0xa7, 0xab, 0xc6, 0xe9, 0x27, 0x19, 0x38, 0x9f, 0x78, 0x31, 0x07, 0x7d, 0x39, 0x41, - 0x0b, 0xde, 0x4d, 0xf9, 0x06, 0xd0, 0x88, 0x7a, 0x70, 0xd2, 0xc2, 0xa0, 0xdf, 0x88, 0x16, 0xe4, - 0xf0, 0x30, 0xe1, 0xe0, 0x0c, 0xee, 0x32, 0x8d, 0x59, 0x9b, 0xa3, 0xff, 0x4a, 0x16, 0xae, 0x8c, - 0x4a, 0xe8, 0xa7, 0xb4, 0x76, 0xd3, 0x53, 0x6a, 0x37, 0x1f, 0x8d, 0x85, 0x3a, 0x9b, 0x32, 0xce, - 0x2f, 0x65, 0xa5, 0xd9, 0xeb, 0x97, 0xcf, 0x91, 0x92, 0x0b, 0xd3, 0xd4, 0x8b, 0x09, 0x9e, 0xa5, - 0x08, 0x55, 0xe1, 0x74, 0x8d, 0x37, 0xbf, 0xd3, 0x2d, 0x2f, 0x89, 0xdb, 0xef, 0x35, 0xe2, 0x8b, - 0x46, 0x1c, 0x74, 0x42, 0x57, 0xa0, 0xe0, 0x72, 0x68, 0x50, 0xad, 0x26, 0x12, 0x26, 0xbc, 0x0d, - 0x4b, 0x28, 0xfa, 0x4c, 0xc4, 0xed, 0xcb, 0x9d, 0xd5, 0xdd, 0x90, 0x61, 0x79, 0xa0, 0xd7, 0xa1, - 0xe0, 0x05, 0x6f, 0x56, 0xf0, 0xd3, 0xc1, 0x67, 0x46, 0x2c, 0x82, 0xa4, 0x51, 0x42, 0xf0, 0x80, - 0x05, 0x9f, 0x9f, 0x7c, 0xde, 0x42, 0x92, 0xd4, 0xdf, 0xd6, 0xa0, 0x28, 0x56, 0xe2, 0x11, 0xd4, - 0x5c, 0xde, 0x53, 0x6b, 0x2e, 0xaf, 0xa5, 0xa2, 0x17, 0x06, 0x14, 0x5c, 0xde, 0x83, 0xd9, 0xe8, - 0xbd, 0x4b, 0xf4, 0x6a, 0x44, 0xaf, 0x69, 0x93, 0xdc, 0xef, 0x0a, 0x34, 0x5f, 0xa8, 0xf3, 0xf4, - 0xb7, 0xa6, 0xe5, 0x57, 0x64, 0x95, 0x9d, 0x51, 0xf9, 0xd2, 0x86, 0xca, 0x57, 0x74, 0x79, 0x33, - 0xa9, 0x2f, 0x2f, 0xba, 0x0d, 0x85, 0x40, 0xf9, 0x08, 0x13, 0xfd, 0x54, 0xb4, 0x5a, 0x85, 0xda, - 0x79, 0x4a, 0x2c, 0x22, 0x94, 0x2c, 0x62, 0x90, 0x6b, 0x28, 0x95, 0xa2, 0x24, 0x83, 0xde, 0x80, - 0xe2, 0x03, 0xc7, 0xbd, 0x6f, 0x39, 0x06, 0x7b, 0x16, 0x06, 0xd2, 0x38, 0xc3, 0x95, 0x27, 0x27, - 0xbc, 0x4a, 0xef, 0x6e, 0x48, 0x1f, 0x47, 0x99, 0xa1, 0x0a, 0x2c, 0xb4, 0x4c, 0x1b, 0x13, 0xa3, - 0x21, 0x4b, 0x2b, 0x73, 0xfc, 0x29, 0x8c, 0xc0, 0x81, 0xdd, 0x51, 0xc1, 0x38, 0x8e, 0x8f, 0x3e, - 0x09, 0x05, 0x4f, 0xdc, 0xed, 0x4c, 0xe7, 0xb4, 0x5d, 0x86, 0x3e, 0x9c, 0x68, 0xf8, 0xed, 0x82, - 0x16, 0x2c, 0x19, 0xa2, 0x6d, 0x58, 0x76, 0xc5, 0xed, 0x29, 0xe5, 0xa5, 0x37, 0x7e, 0xbc, 0xca, - 0x5e, 0x5c, 0xc0, 0x09, 0x70, 0x9c, 0xd8, 0x8b, 0x7a, 0x28, 0xec, 0x02, 0x31, 0x3f, 0x6e, 0x2d, - 0x84, 0x1e, 0x0a, 0x13, 0xf8, 0x06, 0x16, 0xd0, 0x61, 0xa5, 0xba, 0x85, 0x09, 0x4a, 0x75, 0xef, - 0xc2, 0x8c, 0x4b, 0x98, 0x9b, 0x5f, 0x09, 0x52, 0x71, 0x63, 0xd7, 0x00, 0xe0, 0x80, 0x00, 0x0e, - 0x69, 0xd1, 0x25, 0x32, 0xd4, 0x87, 0x1b, 0x6e, 0xa7, 0xf8, 0x7e, 0xa8, 0x58, 0xa6, 0x01, 0xd7, - 0x24, 0xf5, 0xff, 0x9c, 0x83, 0x39, 0x25, 0x9a, 0x45, 0x4f, 0x41, 0x9e, 0xdd, 0x4f, 0x63, 0x3b, - 0xb9, 0x10, 0x6a, 0x1b, 0x76, 0xa1, 0x0d, 0x73, 0x18, 0xfa, 0x9a, 0x06, 0x0b, 0x6d, 0xe5, 0xe4, - 0x2d, 0x50, 0x72, 0x13, 0x66, 0x54, 0xd4, 0xe3, 0xbc, 0xc8, 0x93, 0x47, 0x2a, 0x33, 0x1c, 0xe7, - 0x4e, 0xf7, 0x8a, 0x28, 0x8b, 0xb1, 0x88, 0xcb, 0xb0, 0x85, 0xab, 0x21, 0x49, 0xac, 0xab, 0x60, - 0x1c, 0xc7, 0xa7, 0x2b, 0xcc, 0x66, 0x37, 0xc9, 0x6b, 0x8b, 0x95, 0x80, 0x00, 0x0e, 0x69, 0xa1, - 0x17, 0x61, 0x5e, 0xdc, 0xd7, 0xdf, 0x75, 0x1a, 0x37, 0x0c, 0xef, 0x50, 0xf8, 0xd8, 0x32, 0x26, - 0x58, 0x57, 0xa0, 0x38, 0x86, 0xcd, 0xe6, 0x16, 0x3e, 0x8a, 0xc0, 0x08, 0x4c, 0xa9, 0x2f, 0x42, - 0xad, 0xab, 0x60, 0x1c, 0xc7, 0x47, 0xef, 0x8b, 0xa8, 0x68, 0x9e, 0xad, 0x90, 0x1b, 0x37, 0x41, - 0x4d, 0x57, 0x60, 0xa1, 0xc3, 0x42, 0x92, 0x46, 0x00, 0x14, 0x5b, 0x47, 0x32, 0xbc, 0xa3, 0x82, - 0x71, 0x1c, 0x1f, 0xbd, 0x00, 0x73, 0x2e, 0x55, 0x44, 0x92, 0x00, 0x4f, 0x61, 0xc8, 0xf3, 0x78, - 0x1c, 0x05, 0x62, 0x15, 0x17, 0xbd, 0x04, 0x4b, 0xe1, 0xcd, 0xe5, 0x80, 0x00, 0xcf, 0x69, 0xc8, - 0x47, 0x11, 0x2a, 0x71, 0x04, 0xdc, 0xdf, 0x07, 0xfd, 0x02, 0x2c, 0x46, 0xbe, 0xc4, 0xa6, 0xdd, - 0x20, 0x0f, 0xc5, 0xed, 0xd2, 0x65, 0x96, 0x17, 0x89, 0xc1, 0x70, 0x1f, 0x36, 0xfa, 0x10, 0xcc, - 0xd7, 0x1d, 0xcb, 0x62, 0xea, 0x88, 0xbf, 0x46, 0xc4, 0xaf, 0x91, 0xf2, 0x0b, 0xb7, 0x0a, 0x04, - 0xc7, 0x30, 0xd1, 0x16, 0x20, 0x67, 0xdf, 0x23, 0xee, 0x11, 0x69, 0xbc, 0xc4, 0x1f, 0x94, 0xa6, - 0xd6, 0x78, 0x4e, 0x2d, 0xca, 0xbb, 0xd5, 0x87, 0x81, 0x13, 0x7a, 0xa1, 0x7d, 0xb8, 0x18, 0x98, - 0x86, 0xfe, 0x1e, 0xa5, 0x92, 0x12, 0xb9, 0x5c, 0xbc, 0x3b, 0x10, 0x13, 0x0f, 0xa1, 0x82, 0x7e, - 0x49, 0xad, 0x0a, 0x9f, 0x4f, 0xe3, 0x7d, 0xc7, 0x78, 0x90, 0x7e, 0x62, 0x49, 0xb8, 0x0b, 0x53, - 0xbc, 0x0e, 0xb3, 0xb4, 0x90, 0xc6, 0x8d, 0xed, 0xe8, 0xe3, 0x27, 0xa1, 0xc9, 0xe0, 0xad, 0x58, - 0x70, 0x42, 0x9f, 0x86, 0x99, 0xfd, 0xe0, 0x25, 0xac, 0xd2, 0x62, 0x1a, 0x66, 0x32, 0xf6, 0xa8, - 0x5b, 0x18, 0x84, 0x4a, 0x00, 0x0e, 0x59, 0xa2, 0xa7, 0xa1, 0x78, 0x63, 0xb7, 0x22, 0x25, 0x7d, - 0x89, 0x49, 0x58, 0x8e, 0x76, 0xc1, 0x51, 0x00, 0xdd, 0xc5, 0xd2, 0x7d, 0x42, 0x6c, 0xc9, 0x43, - 0xf3, 0xdb, 0xef, 0x0d, 0x51, 0x6c, 0x96, 0xe6, 0xc2, 0xb5, 0xd2, 0xb9, 0x18, 0xb6, 0x68, 0xc7, - 0x12, 0x03, 0xbd, 0x0e, 0x45, 0x61, 0x93, 0x98, 0xfe, 0x5b, 0x3e, 0xdd, 0x8d, 0x03, 0x1c, 0x92, - 0xc0, 0x51, 0x7a, 0xe8, 0x39, 0x28, 0xb6, 0xd9, 0x03, 0x41, 0xe4, 0x7a, 0xc7, 0xb2, 0x4a, 0xe7, - 0x99, 0x6e, 0x96, 0xe7, 0xff, 0xbb, 0x21, 0x08, 0x47, 0xf1, 0xd0, 0x33, 0x41, 0x8e, 0xfa, 0x5d, - 0x4a, 0x3a, 0x47, 0xe6, 0xa8, 0xa5, 0xd3, 0x3b, 0xa0, 0xb2, 0xef, 0xc2, 0x09, 0x67, 0x14, 0x9f, - 0x0b, 0xcf, 0x68, 0xe5, 0x1b, 0x18, 0x9f, 0x8a, 0x4a, 0x83, 0x96, 0xc6, 0xb3, 0xd7, 0x7d, 0xcf, - 0xac, 0x71, 0x63, 0x91, 0x28, 0x0b, 0x6d, 0x29, 0xff, 0xa9, 0x5c, 0x97, 0x55, 0xdf, 0xf7, 0xe0, - 0xd5, 0xe4, 0xaa, 0xf4, 0xeb, 0xdf, 0xcf, 0xc9, 0x73, 0x9a, 0x58, 0x6a, 0xd6, 0x85, 0xbc, 0xe9, - 0xf9, 0xa6, 0x93, 0x62, 0x89, 0x7f, 0xec, 0x61, 0x0c, 0x56, 0x6a, 0xc6, 0x00, 0x98, 0xb3, 0xa2, - 0x3c, 0xed, 0xa6, 0x69, 0x3f, 0x14, 0xd3, 0xbf, 0x9d, 0x7a, 0xce, 0x95, 0xf3, 0x64, 0x00, 0xcc, - 0x59, 0xa1, 0x7b, 0x90, 0x35, 0xac, 0xfd, 0x94, 0x9e, 0x38, 0x8f, 0xff, 0x9b, 0x00, 0x5e, 0xa8, - 0x51, 0xd9, 0xae, 0x62, 0xca, 0x84, 0xf2, 0xf2, 0x5a, 0xa6, 0xf0, 0x2f, 0x26, 0xe4, 0x55, 0xdb, - 0xd9, 0x4c, 0xe2, 0x55, 0xdb, 0xd9, 0xc4, 0x94, 0x09, 0xfa, 0xa2, 0x06, 0x60, 0xc8, 0x27, 0xfc, - 0xd3, 0x79, 0x93, 0x70, 0xd0, 0xbf, 0x04, 0xe0, 0x15, 0x54, 0x21, 0x14, 0x47, 0x38, 0xeb, 0x6f, - 0x6a, 0xb0, 0xd4, 0x37, 0xd8, 0xf8, 0x7f, 0x37, 0xd0, 0x46, 0xff, 0xef, 0x06, 0xe2, 0xe9, 0x94, - 0x5a, 0xdb, 0x32, 0x13, 0xaf, 0xc9, 0xec, 0xc5, 0xe0, 0xb8, 0xaf, 0x87, 0xfe, 0x2d, 0x0d, 0x8a, - 0x91, 0x12, 0x67, 0xea, 0xf7, 0xb2, 0x52, 0x70, 0x31, 0x8c, 0xf0, 0xd5, 0x18, 0x76, 0x34, 0xc5, - 0x61, 0xfc, 0x94, 0xb4, 0x19, 0x9e, 0x15, 0x46, 0x4e, 0x49, 0x69, 0x2b, 0x16, 0x50, 0x74, 0x19, - 0x72, 0x9e, 0x4f, 0xda, 0x4c, 0xa2, 0x22, 0x15, 0xcf, 0x2c, 0x57, 0xc0, 0x20, 0x8c, 0x1d, 0x55, - 0x8e, 0xa2, 0x7c, 0x25, 0xf2, 0x48, 0x8d, 0x41, 0xdd, 0x6c, 0x06, 0x43, 0x97, 0x20, 0x4b, 0xec, - 0x86, 0xf0, 0x16, 0x8b, 0x02, 0x25, 0x7b, 0xcd, 0x6e, 0x60, 0xda, 0xae, 0xdf, 0x82, 0xd9, 0x1a, - 0xa9, 0xbb, 0xc4, 0x7f, 0x99, 0x1c, 0x8f, 0x76, 0x8e, 0x77, 0x89, 0xe7, 0x3f, 0x33, 0x2a, 0x41, - 0xda, 0x9d, 0xb6, 0xeb, 0x7f, 0xa8, 0x41, 0xec, 0xcd, 0x20, 0xa4, 0xc7, 0x52, 0x9a, 0xd0, 0x9f, - 0xce, 0x54, 0xe2, 0xff, 0xcc, 0xd0, 0xf8, 0x7f, 0x0b, 0x50, 0xcb, 0xf0, 0xeb, 0x87, 0x62, 0x7d, - 0xc4, 0xf3, 0x54, 0xdc, 0x51, 0x0f, 0x2f, 0x54, 0xf4, 0x61, 0xe0, 0x84, 0x5e, 0xfa, 0x12, 0x2c, - 0xc8, 0x28, 0x9e, 0x4b, 0x86, 0xfe, 0xed, 0x2c, 0xcc, 0x2a, 0xaf, 0x43, 0x9f, 0xfc, 0x45, 0x46, - 0x1f, 0x7b, 0x42, 0x34, 0x9e, 0x1d, 0x33, 0x1a, 0x8f, 0x1e, 0x7f, 0xe4, 0xce, 0xf6, 0xf8, 0x23, - 0x9f, 0xce, 0xf1, 0x87, 0x0f, 0xd3, 0xe2, 0xff, 0x91, 0x88, 0x8a, 0xb9, 0x9d, 0x94, 0xae, 0x44, - 0x8a, 0x2b, 0x5e, 0xac, 0x48, 0x30, 0xd8, 0xe5, 0x01, 0x2b, 0xfd, 0x1b, 0x79, 0x98, 0x57, 0x2f, - 0x49, 0x8e, 0xb0, 0x92, 0xef, 0xeb, 0x5b, 0xc9, 0x31, 0x43, 0x9c, 0xec, 0xa4, 0x21, 0x4e, 0x6e, - 0xd2, 0x10, 0x27, 0x7f, 0x8a, 0x10, 0xa7, 0x3f, 0x40, 0x99, 0x1a, 0x39, 0x40, 0xf9, 0xb0, 0x4c, - 0x93, 0x4d, 0x2b, 0xe7, 0xca, 0x61, 0x9a, 0x0c, 0xa9, 0xcb, 0xb0, 0xee, 0x34, 0x12, 0xd3, 0x8d, - 0x85, 0x13, 0x6a, 0xf0, 0xdc, 0xc4, 0xac, 0xd6, 0xf8, 0xa7, 0x28, 0xef, 0x1a, 0x23, 0xa3, 0x15, - 0xfe, 0xcb, 0x1d, 0x66, 0x21, 0x40, 0xb5, 0x2e, 0xb5, 0x10, 0x84, 0xa3, 0x78, 0xec, 0xf9, 0x65, - 0xf5, 0x71, 0x68, 0x16, 0x31, 0x46, 0x9f, 0x5f, 0x8e, 0x3d, 0x26, 0x1d, 0xc7, 0xd7, 0x3f, 0x9b, - 0x81, 0xf0, 0x81, 0x6b, 0xf6, 0x12, 0x95, 0x17, 0x51, 0xd3, 0xc2, 0x99, 0xda, 0x9a, 0xf4, 0xb9, - 0xb7, 0x90, 0xa2, 0x48, 0x48, 0x47, 0x5a, 0xb0, 0xc2, 0xf1, 0xbf, 0xe1, 0x61, 0x6b, 0x03, 0x16, - 0x62, 0x75, 0xb9, 0xa9, 0x17, 0xb8, 0x7c, 0x2b, 0x03, 0x33, 0xb2, 0xb2, 0x99, 0x5a, 0xb6, 0x8e, - 0x1b, 0x3c, 0x9a, 0x23, 0x2d, 0xdb, 0x1d, 0xbc, 0x8d, 0x69, 0x3b, 0x7a, 0x08, 0xd3, 0x87, 0xc4, - 0x68, 0x10, 0x37, 0x38, 0xa7, 0xda, 0x49, 0xa9, 0xa4, 0xfa, 0x06, 0xa3, 0x1a, 0xce, 0x85, 0xff, - 0xf6, 0x70, 0xc0, 0x0e, 0xbd, 0x08, 0xf3, 0xbe, 0xd9, 0x22, 0x34, 0xc0, 0x88, 0x58, 0x8d, 0x6c, - 0x78, 0xf8, 0xb3, 0xa7, 0x40, 0x71, 0x0c, 0x9b, 0xaa, 0xb5, 0x7b, 0x9e, 0x63, 0xb3, 0xfb, 0xc7, - 0x39, 0x35, 0x8a, 0xdb, 0xaa, 0xdd, 0xba, 0xc9, 0xae, 0x1f, 0x4b, 0x0c, 0x8a, 0x6d, 0xb2, 0xca, - 0x4e, 0x97, 0x88, 0x94, 0xd5, 0x62, 0x78, 0x0f, 0x85, 0xb7, 0x63, 0x89, 0xa1, 0xdf, 0x81, 0x85, - 0xd8, 0x44, 0x02, 0x0f, 0x41, 0x4b, 0xf6, 0x10, 0x46, 0xfa, 0xbf, 0x41, 0xd5, 0xd5, 0xb7, 0x7e, - 0xb0, 0xf2, 0xd8, 0x77, 0x7e, 0xb0, 0xf2, 0xd8, 0x77, 0x7f, 0xb0, 0xf2, 0xd8, 0x67, 0x7b, 0x2b, - 0xda, 0x5b, 0xbd, 0x15, 0xed, 0x3b, 0xbd, 0x15, 0xed, 0xbb, 0xbd, 0x15, 0xed, 0xfb, 0xbd, 0x15, - 0xed, 0xcd, 0x1f, 0xae, 0x3c, 0xf6, 0x6a, 0x21, 0xf8, 0x98, 0xff, 0x15, 0x00, 0x00, 0xff, 0xff, - 0xfb, 0x73, 0xce, 0x74, 0x36, 0x6d, 0x00, 0x00, + 0xa6, 0xba, 0xaa, 0x53, 0x55, 0xed, 0x19, 0x6f, 0xa2, 0xfc, 0x10, 0x25, 0x04, 0x94, 0x28, 0xcb, + 0xcf, 0x0b, 0x42, 0x20, 0x84, 0x78, 0x40, 0xe4, 0x05, 0xa1, 0x3c, 0x26, 0x22, 0x02, 0x22, 0x2d, + 0x0f, 0xa0, 0xf0, 0x00, 0x1b, 0x90, 0xd2, 0x24, 0x1d, 0x24, 0x04, 0x2f, 0x28, 0x28, 0x12, 0xca, + 0x4a, 0x48, 0xe8, 0xfe, 0xd4, 0xad, 0xba, 0xd5, 0xd5, 0xed, 0x6e, 0x77, 0x79, 0x88, 0x80, 0x37, + 0xf7, 0x3d, 0xe7, 0x9e, 0x73, 0x6f, 0xdd, 0x73, 0xcf, 0xcf, 0x3d, 0xe7, 0x5e, 0xc3, 0x76, 0xd3, + 0xf4, 0x0f, 0x3b, 0xfb, 0xab, 0x75, 0xa7, 0xb5, 0x66, 0xb8, 0x4d, 0xa7, 0xed, 0x3a, 0xf7, 0xd8, + 0x1f, 0xef, 0x77, 0x1d, 0xcb, 0x72, 0x3a, 0xbe, 0xb7, 0xd6, 0xbe, 0xdf, 0x5c, 0x33, 0xda, 0xa6, + 0xb7, 0x26, 0x5b, 0x8e, 0x3e, 0x68, 0x58, 0xed, 0x43, 0xe3, 0x83, 0x6b, 0x4d, 0x62, 0x13, 0xd7, + 0xf0, 0x49, 0x63, 0xb5, 0xed, 0x3a, 0xbe, 0x83, 0x3e, 0x1c, 0x52, 0x5b, 0x0d, 0xa8, 0xb1, 0x3f, + 0x7e, 0x31, 0xe8, 0xbb, 0xda, 0xbe, 0xdf, 0x5c, 0xa5, 0xd4, 0x56, 0x65, 0x4b, 0x40, 0xed, 0xe2, + 0xfb, 0x23, 0x63, 0x69, 0x3a, 0x4d, 0x67, 0x8d, 0x11, 0xdd, 0xef, 0x1c, 0xb0, 0x5f, 0xec, 0x07, + 0xfb, 0x8b, 0x33, 0xbb, 0xf8, 0xd4, 0xfd, 0xe7, 0xbd, 0x55, 0xd3, 0xa1, 0x63, 0x5b, 0xdb, 0x37, + 0xfc, 0xfa, 0xe1, 0xda, 0x51, 0xdf, 0x88, 0x2e, 0xea, 0x11, 0xa4, 0xba, 0xe3, 0x92, 0x24, 0x9c, + 0x67, 0x43, 0x9c, 0x96, 0x51, 0x3f, 0x34, 0x6d, 0xe2, 0x1e, 0x87, 0xb3, 0x6e, 0x11, 0xdf, 0x48, + 0xea, 0xb5, 0x36, 0xa8, 0x97, 0xdb, 0xb1, 0x7d, 0xb3, 0x45, 0xfa, 0x3a, 0xfc, 0xec, 0x49, 0x1d, + 0xbc, 0xfa, 0x21, 0x69, 0x19, 0x7d, 0xfd, 0x9e, 0x19, 0xd4, 0xaf, 0xe3, 0x9b, 0xd6, 0x9a, 0x69, + 0xfb, 0x9e, 0xef, 0xc6, 0x3b, 0xe9, 0xff, 0xae, 0xc1, 0x52, 0x65, 0xbb, 0xba, 0xe7, 0x1a, 0x07, + 0x07, 0x66, 0x1d, 0x3b, 0x1d, 0xdf, 0xb4, 0x9b, 0xe8, 0xbd, 0x30, 0x6d, 0xda, 0x4d, 0x97, 0x78, + 0x5e, 0x49, 0xbb, 0xac, 0x5d, 0x99, 0xa9, 0x2e, 0xbc, 0xd5, 0x2d, 0x3f, 0xd6, 0xeb, 0x96, 0xa7, + 0x37, 0x79, 0x33, 0x0e, 0xe0, 0xe8, 0x39, 0x28, 0x7a, 0xc4, 0x3d, 0x32, 0xeb, 0x64, 0xd7, 0x71, + 0xfd, 0x52, 0xe6, 0xb2, 0x76, 0x25, 0x5f, 0x3d, 0x27, 0xd0, 0x8b, 0xb5, 0x10, 0x84, 0xa3, 0x78, + 0xb4, 0x9b, 0xeb, 0x38, 0xbe, 0x80, 0x97, 0xb2, 0x8c, 0x8b, 0xec, 0x86, 0x43, 0x10, 0x8e, 0xe2, + 0xa1, 0x0d, 0x58, 0x34, 0x6c, 0xdb, 0xf1, 0x0d, 0xdf, 0x74, 0xec, 0x5d, 0x97, 0x1c, 0x98, 0x0f, + 0x4b, 0x39, 0xd6, 0xb7, 0x24, 0xfa, 0x2e, 0x56, 0x62, 0x70, 0xdc, 0xd7, 0x43, 0xdf, 0x80, 0x52, + 0xa5, 0xb5, 0x6f, 0x78, 0x9e, 0xd1, 0x70, 0xdc, 0xd8, 0xd4, 0xaf, 0x40, 0xa1, 0x65, 0xb4, 0xdb, + 0xa6, 0xdd, 0xa4, 0x73, 0xcf, 0x5e, 0x99, 0xa9, 0xce, 0xf6, 0xba, 0xe5, 0xc2, 0x8e, 0x68, 0xc3, + 0x12, 0xaa, 0xff, 0x7d, 0x06, 0x8a, 0x15, 0xdb, 0xb0, 0x8e, 0x3d, 0xd3, 0xc3, 0x1d, 0x1b, 0x7d, + 0x1c, 0x0a, 0x54, 0x06, 0x1a, 0x86, 0x6f, 0xb0, 0xaf, 0x56, 0xbc, 0xfa, 0x81, 0x55, 0xbe, 0x24, + 0xab, 0xd1, 0x25, 0x09, 0x25, 0x9b, 0x62, 0xaf, 0x1e, 0x7d, 0x70, 0xf5, 0xd6, 0xfe, 0x3d, 0x52, + 0xf7, 0x77, 0x88, 0x6f, 0x54, 0x91, 0x98, 0x05, 0x84, 0x6d, 0x58, 0x52, 0x45, 0x0e, 0xe4, 0xbc, + 0x36, 0xa9, 0xb3, 0x8f, 0x5c, 0xbc, 0xba, 0xb3, 0x3a, 0xc9, 0x2e, 0x5a, 0x8d, 0x0c, 0xbd, 0xd6, + 0x26, 0xf5, 0xea, 0xac, 0x60, 0x9d, 0xa3, 0xbf, 0x30, 0x63, 0x84, 0x1e, 0xc0, 0x94, 0xe7, 0x1b, + 0x7e, 0xc7, 0x63, 0x0b, 0x54, 0xbc, 0x7a, 0x2b, 0x3d, 0x96, 0x8c, 0x6c, 0x75, 0x5e, 0x30, 0x9d, + 0xe2, 0xbf, 0xb1, 0x60, 0xa7, 0xff, 0x83, 0x06, 0xe7, 0x22, 0xd8, 0x15, 0xb7, 0xd9, 0x69, 0x11, + 0xdb, 0x47, 0x97, 0x21, 0x67, 0x1b, 0x2d, 0x22, 0xa4, 0x52, 0x0e, 0xf9, 0xa6, 0xd1, 0x22, 0x98, + 0x41, 0xd0, 0x53, 0x90, 0x3f, 0x32, 0xac, 0x0e, 0x61, 0x1f, 0x69, 0xa6, 0x3a, 0x27, 0x50, 0xf2, + 0xaf, 0xd0, 0x46, 0xcc, 0x61, 0xe8, 0x53, 0x30, 0xc3, 0xfe, 0xb8, 0xee, 0x3a, 0xad, 0x94, 0xa6, + 0x26, 0x46, 0xf8, 0x4a, 0x40, 0xb6, 0x3a, 0xd7, 0xeb, 0x96, 0x67, 0xe4, 0x4f, 0x1c, 0x32, 0xd4, + 0xff, 0x51, 0x83, 0x85, 0xc8, 0xe4, 0xb6, 0x4d, 0xcf, 0x47, 0x1f, 0xed, 0x13, 0x9e, 0xd5, 0xd1, + 0x84, 0x87, 0xf6, 0x66, 0xa2, 0xb3, 0x28, 0x66, 0x5a, 0x08, 0x5a, 0x22, 0x82, 0x63, 0x43, 0xde, + 0xf4, 0x49, 0xcb, 0x2b, 0x65, 0x2e, 0x67, 0xaf, 0x14, 0xaf, 0x6e, 0xa6, 0xb6, 0x8c, 0xe1, 0xf7, + 0xdd, 0xa4, 0xf4, 0x31, 0x67, 0xa3, 0xff, 0x4e, 0x46, 0x99, 0x21, 0x95, 0x28, 0xe4, 0xc0, 0x74, + 0x8b, 0xf8, 0xae, 0x59, 0xe7, 0xfb, 0xaa, 0x78, 0x75, 0x63, 0xb2, 0x51, 0xec, 0x30, 0x62, 0xa1, + 0x66, 0xe2, 0xbf, 0x3d, 0x1c, 0x70, 0x41, 0x87, 0x90, 0x33, 0xdc, 0x66, 0x30, 0xe7, 0xeb, 0xe9, + 0xac, 0x6f, 0x28, 0x73, 0x15, 0xb7, 0xe9, 0x61, 0xc6, 0x01, 0xad, 0xc1, 0x8c, 0x4f, 0xdc, 0x96, + 0x69, 0x1b, 0x3e, 0x57, 0x65, 0x85, 0xea, 0x92, 0x40, 0x9b, 0xd9, 0x0b, 0x00, 0x38, 0xc4, 0xd1, + 0xdf, 0xce, 0xc0, 0x52, 0xdf, 0x66, 0x40, 0xcf, 0x42, 0xbe, 0x7d, 0x68, 0x78, 0x81, 0x74, 0xaf, + 0x04, 0x9f, 0x76, 0x97, 0x36, 0xbe, 0xd3, 0x2d, 0xcf, 0x05, 0x5d, 0x58, 0x03, 0xe6, 0xc8, 0x54, + 0x57, 0xb7, 0x88, 0xe7, 0x19, 0xcd, 0x40, 0xe4, 0x23, 0x5f, 0x84, 0x35, 0xe3, 0x00, 0x8e, 0x7e, + 0x59, 0x83, 0x39, 0xfe, 0x75, 0x30, 0xf1, 0x3a, 0x96, 0x4f, 0xb7, 0x35, 0xfd, 0x36, 0x5b, 0x69, + 0xac, 0x04, 0x27, 0x59, 0x3d, 0x2f, 0xb8, 0xcf, 0x45, 0x5b, 0x3d, 0xac, 0xf2, 0x45, 0x77, 0x61, + 0xc6, 0xf3, 0x0d, 0xd7, 0x27, 0x8d, 0x8a, 0xcf, 0x14, 0x78, 0xf1, 0xea, 0xff, 0x1f, 0x4d, 0xde, + 0xf7, 0xcc, 0x16, 0xe1, 0x7b, 0xab, 0x16, 0x10, 0xc0, 0x21, 0x2d, 0xfd, 0x6f, 0x55, 0xc5, 0x51, + 0xf3, 0xa9, 0xb1, 0x6b, 0x1e, 0xa3, 0xd7, 0xe0, 0x71, 0xaf, 0x53, 0xaf, 0x13, 0xcf, 0x3b, 0xe8, + 0x58, 0xb8, 0x63, 0xdf, 0x30, 0x3d, 0xdf, 0x71, 0x8f, 0xb7, 0xcd, 0x96, 0xe9, 0xb3, 0xef, 0x9d, + 0xaf, 0x5e, 0xea, 0x75, 0xcb, 0x8f, 0xd7, 0x06, 0x21, 0xe1, 0xc1, 0xfd, 0x91, 0x01, 0x4f, 0x74, + 0xec, 0xc1, 0xe4, 0xb9, 0x4d, 0x2c, 0xf7, 0xba, 0xe5, 0x27, 0xee, 0x0c, 0x46, 0xc3, 0xc3, 0x68, + 0xe8, 0xff, 0xaa, 0xc1, 0x62, 0x30, 0xaf, 0x3d, 0xd2, 0x6a, 0x5b, 0x86, 0x4f, 0x1e, 0x81, 0xc5, + 0xf1, 0x15, 0x8b, 0x83, 0xd3, 0xd1, 0x1b, 0xc1, 0xf8, 0x07, 0x99, 0x1d, 0xfd, 0x5f, 0x34, 0x58, + 0x8e, 0x23, 0x3f, 0x02, 0x2d, 0xe9, 0xa9, 0x5a, 0xf2, 0x66, 0xba, 0xb3, 0x1d, 0xa0, 0x2a, 0x7f, + 0x94, 0x30, 0xd7, 0xff, 0xe1, 0xfa, 0x52, 0xff, 0xc3, 0x1c, 0xcc, 0x56, 0x6c, 0xdf, 0xac, 0x1c, + 0x1c, 0x98, 0xb6, 0xe9, 0x1f, 0xa3, 0x2f, 0x67, 0x60, 0xad, 0xed, 0x92, 0x03, 0xe2, 0xba, 0xa4, + 0xb1, 0xd1, 0x71, 0x4d, 0xbb, 0x59, 0xab, 0x1f, 0x92, 0x46, 0xc7, 0x32, 0xed, 0xe6, 0x66, 0xd3, + 0x76, 0x64, 0xf3, 0xb5, 0x87, 0xa4, 0xde, 0xa1, 0xae, 0x9c, 0x58, 0xff, 0xd6, 0x64, 0xc3, 0xdc, + 0x1d, 0x8f, 0x69, 0xf5, 0x99, 0x5e, 0xb7, 0xbc, 0x36, 0x66, 0x27, 0x3c, 0xee, 0xd4, 0xd0, 0x97, + 0x32, 0xb0, 0xea, 0x92, 0x4f, 0x74, 0xcc, 0xd1, 0xbf, 0x06, 0xdf, 0xa0, 0xd6, 0x64, 0x5f, 0x03, + 0x8f, 0xc5, 0xb3, 0x7a, 0xb5, 0xd7, 0x2d, 0x8f, 0xd9, 0x07, 0x8f, 0x39, 0x2f, 0xfd, 0xcf, 0x35, + 0x28, 0x8c, 0xe1, 0xfd, 0x95, 0x55, 0xef, 0x6f, 0xa6, 0xcf, 0xf3, 0xf3, 0xfb, 0x3d, 0xbf, 0x97, + 0x26, 0xfb, 0x68, 0xa3, 0x78, 0x7c, 0xff, 0x46, 0xa3, 0xac, 0xb8, 0x87, 0x88, 0x0e, 0x61, 0xb9, + 0xed, 0x34, 0x82, 0x4d, 0x7f, 0xc3, 0xf0, 0x0e, 0x19, 0x4c, 0x4c, 0xef, 0xd9, 0x5e, 0xb7, 0xbc, + 0xbc, 0x9b, 0x00, 0x7f, 0xa7, 0x5b, 0x2e, 0x49, 0x22, 0x31, 0x04, 0x9c, 0x48, 0x11, 0xb5, 0xa1, + 0x70, 0x60, 0x12, 0xab, 0x81, 0xc9, 0x81, 0x90, 0x94, 0x09, 0xb7, 0xf7, 0x75, 0x41, 0x8d, 0x07, + 0x47, 0xc1, 0x2f, 0x2c, 0xb9, 0xe8, 0x3f, 0xc9, 0xc1, 0x42, 0xd5, 0xea, 0x90, 0x97, 0x5c, 0x42, + 0x02, 0xff, 0xa6, 0x02, 0x0b, 0x6d, 0x97, 0x1c, 0x99, 0xe4, 0x41, 0x8d, 0x58, 0xa4, 0xee, 0x3b, + 0xae, 0x98, 0xea, 0x05, 0xb1, 0x92, 0x0b, 0xbb, 0x2a, 0x18, 0xc7, 0xf1, 0xd1, 0x8b, 0x30, 0x6f, + 0xd4, 0x7d, 0xf3, 0x88, 0x48, 0x0a, 0x7c, 0xa1, 0xdf, 0x25, 0x28, 0xcc, 0x57, 0x14, 0x28, 0x8e, + 0x61, 0xa3, 0x8f, 0x42, 0xc9, 0xab, 0x1b, 0x16, 0xb9, 0xd3, 0x16, 0xac, 0xd6, 0x0f, 0x49, 0xfd, + 0xfe, 0xae, 0x63, 0xda, 0xbe, 0x70, 0xdc, 0x2e, 0x0b, 0x4a, 0xa5, 0xda, 0x00, 0x3c, 0x3c, 0x90, + 0x02, 0xfa, 0x53, 0x0d, 0x2e, 0xb5, 0x5d, 0xb2, 0xeb, 0x3a, 0x2d, 0x87, 0x4a, 0x6f, 0x9f, 0x8b, + 0x27, 0x5c, 0x9d, 0x57, 0x26, 0xdc, 0xa6, 0xbc, 0xa5, 0x3f, 0x9a, 0x7a, 0x77, 0xaf, 0x5b, 0xbe, + 0xb4, 0x3b, 0x6c, 0x00, 0x78, 0xf8, 0xf8, 0xd0, 0x9f, 0x69, 0xb0, 0xd2, 0x76, 0x3c, 0x7f, 0xc8, + 0x14, 0xf2, 0x67, 0x3a, 0x05, 0xbd, 0xd7, 0x2d, 0xaf, 0xec, 0x0e, 0x1d, 0x01, 0x3e, 0x61, 0x84, + 0x7a, 0xaf, 0x08, 0x4b, 0x11, 0xd9, 0x13, 0x1e, 0xe0, 0x0b, 0x30, 0x17, 0x08, 0x03, 0x3f, 0x73, + 0xe0, 0xb2, 0x27, 0xfd, 0xd5, 0x4a, 0x14, 0x88, 0x55, 0x5c, 0x2a, 0x77, 0x52, 0x14, 0x79, 0xef, + 0x98, 0xdc, 0xed, 0x2a, 0x50, 0x1c, 0xc3, 0x46, 0x9b, 0x70, 0x4e, 0xb4, 0x60, 0xd2, 0xb6, 0xcc, + 0xba, 0xb1, 0xee, 0x74, 0x84, 0xc8, 0xe5, 0xab, 0x17, 0x7a, 0xdd, 0xf2, 0xb9, 0xdd, 0x7e, 0x30, + 0x4e, 0xea, 0x83, 0xb6, 0x61, 0xd9, 0xe8, 0xf8, 0x8e, 0x9c, 0xff, 0x35, 0xdb, 0xd8, 0xb7, 0x48, + 0x83, 0x89, 0x56, 0xa1, 0x5a, 0xa2, 0x5a, 0xa3, 0x92, 0x00, 0xc7, 0x89, 0xbd, 0xd0, 0x6e, 0x8c, + 0x5a, 0x8d, 0xd4, 0x1d, 0xbb, 0xc1, 0x57, 0x39, 0x5f, 0x7d, 0x52, 0x4c, 0x4f, 0xa5, 0x28, 0x70, + 0x70, 0x62, 0x4f, 0x64, 0xc1, 0x7c, 0xcb, 0x78, 0x78, 0xc7, 0x36, 0x8e, 0x0c, 0xd3, 0xa2, 0x4c, + 0x4a, 0x53, 0x27, 0xb8, 0xa6, 0x1d, 0xdf, 0xb4, 0x56, 0xf9, 0xf9, 0xd4, 0xea, 0xa6, 0xed, 0xdf, + 0x72, 0x6b, 0x3e, 0x35, 0x02, 0x55, 0x44, 0x3f, 0xec, 0x8e, 0x42, 0x0b, 0xc7, 0x68, 0xa3, 0x5b, + 0x70, 0x9e, 0x6d, 0xc7, 0x0d, 0xe7, 0x81, 0xbd, 0x41, 0x2c, 0xe3, 0x38, 0x98, 0xc0, 0x34, 0x9b, + 0xc0, 0xe3, 0xbd, 0x6e, 0xf9, 0x7c, 0x2d, 0x09, 0x01, 0x27, 0xf7, 0xa3, 0xbe, 0xbc, 0x0a, 0xc0, + 0xe4, 0xc8, 0xf4, 0x4c, 0xc7, 0xe6, 0xbe, 0x7c, 0x21, 0xf4, 0xe5, 0x6b, 0x83, 0xd1, 0xf0, 0x30, + 0x1a, 0xe8, 0xb7, 0x35, 0x58, 0x4e, 0xda, 0x86, 0xa5, 0x99, 0x34, 0xce, 0x75, 0x62, 0x5b, 0x8b, + 0x4b, 0x44, 0xa2, 0x52, 0x48, 0x1c, 0x04, 0xfa, 0xac, 0x06, 0xb3, 0x46, 0xc4, 0x39, 0x2b, 0x01, + 0x1b, 0xd5, 0xd6, 0xa4, 0xde, 0x70, 0x48, 0xb1, 0xba, 0xd8, 0xeb, 0x96, 0x15, 0x07, 0x10, 0x2b, + 0x1c, 0xd1, 0xef, 0x6a, 0x70, 0x3e, 0x71, 0x8f, 0x97, 0x8a, 0x67, 0xf1, 0x85, 0x98, 0x90, 0x24, + 0xeb, 0x9c, 0xe4, 0x61, 0xa0, 0x37, 0x35, 0x69, 0xca, 0x76, 0x82, 0x78, 0x64, 0x96, 0x0d, 0xed, + 0xf6, 0x84, 0xfe, 0x68, 0x68, 0xbd, 0x03, 0xc2, 0xd5, 0x73, 0x11, 0xcb, 0x18, 0x34, 0xe2, 0x38, + 0x7b, 0xf4, 0x15, 0x2d, 0x30, 0x8d, 0x72, 0x44, 0x73, 0x67, 0x35, 0x22, 0x14, 0x5a, 0x5a, 0x39, + 0xa0, 0x18, 0x73, 0xf4, 0x31, 0xb8, 0x68, 0xec, 0x3b, 0xae, 0x9f, 0xb8, 0xf9, 0x4a, 0xf3, 0x6c, + 0x1b, 0xad, 0xf4, 0xba, 0xe5, 0x8b, 0x95, 0x81, 0x58, 0x78, 0x08, 0x05, 0xfd, 0x9f, 0xb3, 0x30, + 0xbb, 0x6e, 0xd8, 0x86, 0x7b, 0x2c, 0x4c, 0xd7, 0x37, 0x34, 0x78, 0xb2, 0xde, 0x71, 0x5d, 0x62, + 0xfb, 0x35, 0x9f, 0xb4, 0xfb, 0x0d, 0x97, 0x76, 0xa6, 0x86, 0xeb, 0x72, 0xaf, 0x5b, 0x7e, 0x72, + 0x7d, 0x08, 0x7f, 0x3c, 0x74, 0x74, 0xe8, 0xaf, 0x35, 0xd0, 0x05, 0x42, 0xd5, 0xa8, 0xdf, 0x6f, + 0xba, 0x4e, 0xc7, 0x6e, 0xf4, 0x4f, 0x22, 0x73, 0xa6, 0x93, 0x78, 0xba, 0xd7, 0x2d, 0xeb, 0xeb, + 0x27, 0x8e, 0x02, 0x8f, 0x30, 0x52, 0xf4, 0x12, 0x2c, 0x09, 0xac, 0x6b, 0x0f, 0xdb, 0xc4, 0x35, + 0xa9, 0xef, 0x2b, 0xce, 0xf9, 0x1f, 0x17, 0x66, 0x65, 0x69, 0x3d, 0x8e, 0x80, 0xfb, 0xfb, 0xe8, + 0x7f, 0x9c, 0x03, 0x08, 0x56, 0x9a, 0xb4, 0xd1, 0xcf, 0xc0, 0x8c, 0x47, 0xfc, 0xbb, 0xc4, 0x6c, + 0x1e, 0x06, 0x27, 0x37, 0xfc, 0x38, 0x28, 0x68, 0xc4, 0x21, 0x1c, 0xdd, 0x87, 0x7c, 0xdb, 0xe8, + 0x78, 0x44, 0x7c, 0xb7, 0xad, 0x54, 0xbe, 0xdb, 0x2e, 0xa5, 0xc8, 0x63, 0x0b, 0xf6, 0x27, 0xe6, + 0x3c, 0xd0, 0xe7, 0x35, 0x00, 0xa2, 0xce, 0xb5, 0x78, 0xb5, 0x96, 0x0a, 0xcb, 0xf0, 0x73, 0xd0, + 0x6f, 0x50, 0x9d, 0xef, 0x75, 0xcb, 0x10, 0xf9, 0x6a, 0x11, 0xb6, 0xe8, 0x01, 0x14, 0x8c, 0x40, + 0x5d, 0xe6, 0xce, 0x42, 0x5d, 0x32, 0x97, 0x5f, 0xae, 0xb7, 0x64, 0x86, 0xbe, 0xa4, 0xc1, 0xbc, + 0x47, 0x7c, 0xb1, 0x54, 0x74, 0xd3, 0x0a, 0x5f, 0x71, 0x7b, 0x32, 0xfe, 0x35, 0x85, 0x26, 0x57, + 0x3e, 0x6a, 0x1b, 0x8e, 0xf1, 0xd5, 0xdf, 0x2c, 0xc2, 0x7c, 0x20, 0x32, 0xa1, 0xfb, 0x57, 0xe7, + 0x2d, 0xc9, 0xee, 0xdf, 0x7a, 0x14, 0x88, 0x55, 0x5c, 0xda, 0xd9, 0xf3, 0xa9, 0xbf, 0xa1, 0x7a, + 0x7f, 0xb2, 0x73, 0x2d, 0x0a, 0xc4, 0x2a, 0x2e, 0x6a, 0x41, 0xde, 0xf3, 0x49, 0x3b, 0x38, 0x6c, + 0xbd, 0x31, 0xd9, 0xd7, 0x08, 0x77, 0x42, 0x78, 0xa0, 0x44, 0x7f, 0x79, 0x98, 0x73, 0x41, 0x5f, + 0xd5, 0x60, 0xde, 0x57, 0x72, 0x5a, 0x42, 0x0c, 0xd2, 0x91, 0x44, 0x35, 0x5d, 0xc6, 0x57, 0x43, + 0x6d, 0xc3, 0x31, 0xf6, 0x09, 0x1e, 0x61, 0xfe, 0x0c, 0x3d, 0xc2, 0x57, 0xa1, 0xd0, 0x32, 0x1e, + 0xd6, 0x3a, 0x6e, 0xf3, 0xf4, 0x9e, 0xa7, 0x48, 0xf9, 0x71, 0x2a, 0x58, 0xd2, 0x43, 0x9f, 0xd3, + 0x22, 0x9b, 0x6b, 0x9a, 0x11, 0xbf, 0x9b, 0xee, 0xe6, 0x92, 0x0a, 0x75, 0xe0, 0x36, 0xeb, 0xf3, + 0xcf, 0x0a, 0x8f, 0xdc, 0x3f, 0xa3, 0xbe, 0x06, 0xdf, 0x20, 0xd2, 0xd7, 0x98, 0x39, 0x53, 0x5f, + 0x63, 0x5d, 0x61, 0x86, 0x63, 0xcc, 0xd9, 0x78, 0xf8, 0x9e, 0x93, 0xe3, 0x81, 0x33, 0x1d, 0x4f, + 0x4d, 0x61, 0x86, 0x63, 0xcc, 0x07, 0x07, 0x25, 0xc5, 0xb3, 0x09, 0x4a, 0x66, 0x53, 0x08, 0x4a, + 0x86, 0xfb, 0x6b, 0x73, 0x13, 0xfb, 0x6b, 0x3f, 0xd2, 0xe0, 0xc2, 0xba, 0xd5, 0xf1, 0x7c, 0xe2, + 0xfe, 0xaf, 0xc9, 0x63, 0xfc, 0x87, 0x06, 0x4f, 0x0c, 0x98, 0xf3, 0x23, 0x48, 0x67, 0xbc, 0xa1, + 0xa6, 0x33, 0xee, 0x4c, 0x68, 0x77, 0x92, 0xe7, 0x31, 0x20, 0xab, 0xe1, 0xc3, 0xdc, 0x86, 0xe1, + 0x1b, 0x0d, 0xa7, 0xc9, 0xd3, 0x0c, 0xe8, 0x45, 0x28, 0x98, 0xb6, 0x4f, 0xdc, 0x23, 0xc3, 0x12, + 0x96, 0x57, 0x0f, 0x86, 0xbe, 0x29, 0xda, 0xdf, 0xe9, 0x96, 0xe7, 0x37, 0x3a, 0x2e, 0x2b, 0xd4, + 0xe0, 0x7a, 0x18, 0xcb, 0x3e, 0xe8, 0x29, 0xc8, 0x7f, 0xa2, 0x43, 0xdc, 0xe3, 0x78, 0x5a, 0xff, + 0x36, 0x6d, 0xc4, 0x1c, 0xa6, 0xff, 0x5d, 0x06, 0x22, 0x5e, 0xd1, 0x23, 0x10, 0x2b, 0x5b, 0x11, + 0xab, 0x09, 0xfd, 0x9c, 0x88, 0x8f, 0x37, 0xa8, 0x1e, 0xe3, 0x28, 0x56, 0x8f, 0x71, 0x33, 0x35, + 0x8e, 0xc3, 0xcb, 0x31, 0xde, 0xd6, 0xe0, 0x89, 0x10, 0xb9, 0xdf, 0xd7, 0x3f, 0xf9, 0x60, 0xfe, + 0x39, 0x28, 0x1a, 0x61, 0x37, 0xb1, 0x8a, 0xb2, 0xde, 0x27, 0x42, 0x11, 0x47, 0xf1, 0xc2, 0x94, + 0x78, 0xf6, 0x94, 0x29, 0xf1, 0xdc, 0xf0, 0x94, 0xb8, 0xfe, 0xe3, 0x0c, 0x5c, 0xea, 0x9f, 0x59, + 0x20, 0xdd, 0x98, 0x1c, 0x8c, 0x30, 0xb7, 0xe7, 0x61, 0xd6, 0x17, 0x1d, 0x68, 0xab, 0x98, 0xdc, + 0xb2, 0xc0, 0x9c, 0xdd, 0x8b, 0xc0, 0xb0, 0x82, 0x49, 0x7b, 0xd6, 0xf9, 0xbe, 0xaa, 0xd5, 0x9d, + 0x76, 0x50, 0x3b, 0x20, 0x7b, 0xae, 0x47, 0x60, 0x58, 0xc1, 0x94, 0xc9, 0xba, 0xdc, 0x99, 0x17, + 0x37, 0xd4, 0xe0, 0x7c, 0x90, 0xb3, 0xb9, 0xee, 0xb8, 0xeb, 0x4e, 0xab, 0x6d, 0x11, 0x96, 0x72, + 0xca, 0xb3, 0xc1, 0x5e, 0x12, 0x5d, 0xce, 0xe3, 0x24, 0x24, 0x9c, 0xdc, 0x57, 0x7f, 0x3b, 0x0b, + 0xe7, 0xc2, 0xcf, 0xbe, 0xee, 0xd8, 0x0d, 0x93, 0x65, 0xbe, 0x5e, 0x80, 0x9c, 0x7f, 0xdc, 0x0e, + 0x3e, 0xf6, 0xff, 0x0b, 0x86, 0xb3, 0x77, 0xdc, 0xa6, 0xab, 0x7d, 0x21, 0xa1, 0x0b, 0x05, 0x61, + 0xd6, 0x09, 0x6d, 0xcb, 0xdd, 0xc1, 0x57, 0xe0, 0x59, 0x55, 0x9a, 0xdf, 0xe9, 0x96, 0x13, 0xaa, + 0xfc, 0x56, 0x25, 0x25, 0x55, 0xe6, 0xd1, 0x3d, 0x98, 0xb7, 0x0c, 0xcf, 0xbf, 0xd3, 0x6e, 0x18, + 0x3e, 0xd9, 0x33, 0x5b, 0x44, 0xec, 0xb9, 0x71, 0xea, 0x14, 0xe4, 0xf1, 0xf0, 0xb6, 0x42, 0x09, + 0xc7, 0x28, 0xa3, 0x23, 0x40, 0xb4, 0x65, 0xcf, 0x35, 0x6c, 0x8f, 0xcf, 0x8a, 0xf2, 0x1b, 0xbf, + 0x2e, 0xe2, 0xa2, 0xe0, 0x87, 0xb6, 0xfb, 0xa8, 0xe1, 0x04, 0x0e, 0xe8, 0x69, 0x98, 0x72, 0x89, + 0xe1, 0x89, 0xc5, 0x9c, 0x09, 0xf7, 0x3f, 0x66, 0xad, 0x58, 0x40, 0xa3, 0x1b, 0x6a, 0xea, 0x84, + 0x0d, 0xf5, 0x3d, 0x0d, 0xe6, 0xc3, 0x65, 0x7a, 0x04, 0x66, 0xae, 0xa5, 0x9a, 0xb9, 0x1b, 0x69, + 0xa9, 0xc4, 0x01, 0x96, 0xed, 0x4f, 0x72, 0xd1, 0xf9, 0xb1, 0x4c, 0xfd, 0x27, 0x61, 0x26, 0xd8, + 0xd5, 0x41, 0xae, 0x7e, 0x42, 0x6f, 0x5c, 0xf1, 0x2c, 0x22, 0xa5, 0x44, 0x82, 0x09, 0x0e, 0xf9, + 0x51, 0xc3, 0xda, 0x10, 0x46, 0x53, 0x88, 0xbd, 0x34, 0xac, 0x81, 0x31, 0x4d, 0x32, 0xac, 0x41, + 0x1f, 0x74, 0x07, 0x2e, 0xb4, 0x5d, 0x87, 0xd5, 0x72, 0x6e, 0x10, 0xa3, 0x61, 0x99, 0x36, 0x09, + 0x9c, 0x3e, 0x9e, 0x9d, 0x78, 0xa2, 0xd7, 0x2d, 0x5f, 0xd8, 0x4d, 0x46, 0xc1, 0x83, 0xfa, 0xaa, + 0x25, 0x51, 0xb9, 0x93, 0x4b, 0xa2, 0xd0, 0xaf, 0xc8, 0xd0, 0x8a, 0x78, 0xa5, 0x3c, 0xfb, 0x88, + 0xaf, 0xa5, 0xb5, 0x94, 0x09, 0x6a, 0x3d, 0x14, 0xa9, 0x8a, 0x60, 0x8a, 0x25, 0xfb, 0xc1, 0xfe, + 0xfb, 0xd4, 0xe9, 0xfc, 0x77, 0xfd, 0x0b, 0x79, 0x58, 0x8c, 0x1b, 0xdb, 0xb3, 0x2f, 0xf7, 0xfa, + 0x75, 0x0d, 0x16, 0x03, 0x41, 0xe1, 0x3c, 0x49, 0x70, 0x08, 0xb1, 0x9d, 0x92, 0x7c, 0x72, 0xb7, + 0x41, 0xd6, 0xde, 0xee, 0xc5, 0xb8, 0xe1, 0x3e, 0xfe, 0xe8, 0x75, 0x28, 0xca, 0x58, 0xfd, 0x54, + 0xb5, 0x5f, 0x0b, 0xcc, 0x61, 0x08, 0x49, 0xe0, 0x28, 0x3d, 0xf4, 0x05, 0x0d, 0xa0, 0x1e, 0x68, + 0xf4, 0x40, 0x90, 0x6e, 0xa7, 0x25, 0x48, 0xd2, 0x56, 0x84, 0x7e, 0xa1, 0x6c, 0xf2, 0x70, 0x84, + 0x31, 0xfa, 0x0d, 0x16, 0xa5, 0x4b, 0x47, 0x86, 0x8a, 0x0e, 0x1d, 0xc9, 0x47, 0xd2, 0x16, 0xe9, + 0xf0, 0xec, 0x56, 0x7a, 0x0d, 0x11, 0x90, 0x87, 0x95, 0x41, 0xe8, 0x2f, 0x80, 0xcc, 0xd5, 0xd3, + 0x1d, 0xca, 0xb2, 0xf5, 0xbb, 0x86, 0x7f, 0x28, 0x44, 0x50, 0xee, 0xd0, 0xeb, 0x01, 0x00, 0x87, + 0x38, 0xfa, 0x5f, 0x68, 0xb0, 0xbc, 0xe9, 0xf9, 0xa6, 0xb3, 0x41, 0x3c, 0x9f, 0x6e, 0x5a, 0x6a, + 0xdf, 0x3b, 0x16, 0x19, 0xc1, 0x43, 0xda, 0x80, 0x45, 0x71, 0xa0, 0xd6, 0xd9, 0xf7, 0x88, 0x1f, + 0xf1, 0x92, 0xa4, 0xe8, 0xac, 0xc7, 0xe0, 0xb8, 0xaf, 0x07, 0xa5, 0x22, 0x4e, 0xd6, 0x42, 0x2a, + 0x59, 0x95, 0x4a, 0x2d, 0x06, 0xc7, 0x7d, 0x3d, 0xf4, 0x6f, 0x66, 0xe0, 0x1c, 0x9b, 0x46, 0xac, + 0xf0, 0xfb, 0xd7, 0x34, 0x98, 0x3f, 0x32, 0x5d, 0xbf, 0x63, 0x58, 0xd1, 0x23, 0xc2, 0x89, 0xa5, + 0x87, 0xf1, 0x7a, 0x45, 0x21, 0x1c, 0xfa, 0x05, 0x6a, 0x3b, 0x8e, 0x0d, 0x80, 0x8e, 0x69, 0xa1, + 0xa1, 0x7e, 0xed, 0x74, 0x42, 0xd8, 0xa4, 0x75, 0xe4, 0x89, 0xa6, 0x58, 0x23, 0x8e, 0xf3, 0xd7, + 0x5f, 0x13, 0x9f, 0x4f, 0x1d, 0xfa, 0x08, 0x42, 0xa0, 0xc3, 0x94, 0xeb, 0x74, 0xa8, 0x8d, 0xcc, + 0xb0, 0xba, 0x7a, 0x60, 0x8e, 0x06, 0x6b, 0xc1, 0x02, 0xa2, 0x7f, 0x4d, 0x83, 0x99, 0x2d, 0x67, + 0x5f, 0x04, 0x8d, 0x1f, 0x4b, 0x21, 0x80, 0x93, 0x7a, 0x5e, 0x9e, 0xd6, 0x84, 0xae, 0xc3, 0x8b, + 0x4a, 0xf8, 0xf6, 0x64, 0x84, 0xf6, 0x2a, 0xbb, 0x28, 0x42, 0x49, 0x6d, 0x39, 0xfb, 0x03, 0xe3, + 0xfb, 0xdf, 0xcf, 0xc3, 0xdc, 0xcb, 0xc6, 0x31, 0xb1, 0x7d, 0x43, 0x8c, 0xf8, 0xbd, 0x30, 0x6d, + 0x34, 0x1a, 0x49, 0x17, 0x27, 0x2a, 0xbc, 0x19, 0x07, 0x70, 0x16, 0x11, 0xb5, 0x59, 0x5e, 0x3f, + 0x62, 0xbb, 0xc3, 0x88, 0x28, 0x04, 0xe1, 0x28, 0x5e, 0xb8, 0x95, 0xd6, 0x1d, 0xfb, 0xc0, 0x6c, + 0x26, 0x6d, 0x82, 0xf5, 0x18, 0x1c, 0xf7, 0xf5, 0x40, 0x5b, 0x80, 0x44, 0xd9, 0x5f, 0xa5, 0x5e, + 0x77, 0x3a, 0x36, 0xdf, 0x4c, 0x3c, 0x58, 0x92, 0x4e, 0xe4, 0x4e, 0x1f, 0x06, 0x4e, 0xe8, 0x85, + 0x3e, 0x0a, 0xa5, 0x3a, 0xa3, 0x2c, 0x5c, 0x8a, 0x28, 0x45, 0xee, 0x56, 0xca, 0x9a, 0x9a, 0xf5, + 0x01, 0x78, 0x78, 0x20, 0x05, 0x3a, 0x52, 0xcf, 0x77, 0x5c, 0xa3, 0x49, 0xa2, 0x74, 0xa7, 0xd4, + 0x91, 0xd6, 0xfa, 0x30, 0x70, 0x42, 0x2f, 0xf4, 0x19, 0x98, 0xf1, 0x0f, 0x5d, 0xe2, 0x1d, 0x3a, + 0x56, 0x43, 0x1c, 0xdf, 0x4e, 0x18, 0x41, 0x8b, 0xd5, 0xdf, 0x0b, 0xa8, 0x46, 0x9c, 0x9c, 0xa0, + 0x09, 0x87, 0x3c, 0x91, 0x0b, 0x53, 0x1e, 0x0d, 0xdf, 0xbc, 0x52, 0x21, 0x0d, 0x37, 0x51, 0x70, + 0x67, 0x11, 0x61, 0x24, 0x76, 0x67, 0x1c, 0xb0, 0xe0, 0xa4, 0x7f, 0x3b, 0x03, 0xb3, 0x51, 0xc4, + 0x11, 0x76, 0xea, 0xe7, 0x35, 0x98, 0xad, 0x3b, 0xb6, 0xef, 0x3a, 0x16, 0x8f, 0x4b, 0xf9, 0x06, + 0x99, 0xf0, 0xda, 0x00, 0x23, 0xb5, 0x41, 0x7c, 0xc3, 0xb4, 0x22, 0x21, 0x6e, 0x84, 0x0d, 0x56, + 0x98, 0xa2, 0x2f, 0x6b, 0xb0, 0x10, 0xe6, 0xb5, 0xc2, 0x00, 0x39, 0xd5, 0x81, 0xc8, 0xd2, 0xb3, + 0x6b, 0x2a, 0x27, 0x1c, 0x67, 0xad, 0xef, 0xc3, 0x62, 0x7c, 0xb5, 0xe9, 0xa7, 0x6c, 0x1b, 0x62, + 0xaf, 0x67, 0xc3, 0x4f, 0xb9, 0x6b, 0x78, 0x1e, 0x66, 0x10, 0xf4, 0x3e, 0x28, 0xb4, 0x0c, 0xb7, + 0x69, 0xda, 0x86, 0xc5, 0xbe, 0x62, 0x36, 0xa2, 0x90, 0x44, 0x3b, 0x96, 0x18, 0xfa, 0x0f, 0x73, + 0x50, 0xdc, 0x21, 0x86, 0xd7, 0x71, 0x09, 0x3b, 0xc1, 0x3a, 0x73, 0x17, 0x51, 0xa9, 0xc3, 0xcf, + 0xa6, 0x57, 0x87, 0x8f, 0x5e, 0x05, 0x38, 0x30, 0x6d, 0xd3, 0x3b, 0x3c, 0x65, 0x85, 0x3f, 0xcb, + 0x70, 0x5e, 0x97, 0x14, 0x70, 0x84, 0x5a, 0x78, 0xc5, 0x27, 0x3f, 0xe4, 0x8a, 0xcf, 0x17, 0xb4, + 0x88, 0xf1, 0xe0, 0xce, 0xd7, 0xdd, 0x49, 0x0b, 0xa8, 0xe5, 0xc2, 0xac, 0x06, 0xc6, 0xe4, 0x9a, + 0xed, 0xbb, 0xc7, 0x43, 0x6d, 0xcc, 0x1e, 0x14, 0x5c, 0xe2, 0x75, 0x5a, 0xd4, 0xd9, 0x9d, 0x1e, + 0xfb, 0x33, 0xb0, 0x24, 0x10, 0x16, 0xfd, 0xb1, 0xa4, 0x74, 0xf1, 0x05, 0x98, 0x53, 0x86, 0x80, + 0x16, 0x21, 0x7b, 0x9f, 0x1c, 0x73, 0x39, 0xc1, 0xf4, 0x4f, 0xb4, 0xac, 0x94, 0xc2, 0x8a, 0xcf, + 0xf2, 0xa1, 0xcc, 0xf3, 0x9a, 0xfe, 0xe3, 0x29, 0x98, 0x12, 0xf6, 0xea, 0x64, 0x5d, 0x10, 0x3d, + 0xb8, 0xcd, 0x9c, 0xe2, 0xe0, 0x76, 0x0b, 0x66, 0x4d, 0xdb, 0xf4, 0x4d, 0xc3, 0x62, 0x11, 0x91, + 0xb0, 0x55, 0x4f, 0x07, 0xfb, 0x7f, 0x33, 0x02, 0x4b, 0xa0, 0xa3, 0xf4, 0x45, 0xb7, 0x21, 0xcf, + 0x94, 0xb9, 0x90, 0xa7, 0xf1, 0xf3, 0x7a, 0x2c, 0x67, 0xcf, 0x6b, 0xeb, 0x38, 0x25, 0xe6, 0x53, + 0xf2, 0x4b, 0x17, 0xd2, 0x91, 0x17, 0x62, 0x15, 0xfa, 0x94, 0x31, 0x38, 0xee, 0xeb, 0x41, 0xa9, + 0x1c, 0x18, 0xa6, 0xd5, 0x71, 0x49, 0x48, 0x65, 0x4a, 0xa5, 0x72, 0x3d, 0x06, 0xc7, 0x7d, 0x3d, + 0xd0, 0x01, 0xcc, 0x8a, 0x36, 0x9e, 0xd6, 0x99, 0x3e, 0xe5, 0x2c, 0x59, 0xfa, 0xee, 0x7a, 0x84, + 0x12, 0x56, 0xe8, 0xa2, 0x0e, 0x2c, 0x99, 0x76, 0xdd, 0xb1, 0xeb, 0x56, 0xc7, 0x33, 0x8f, 0x48, + 0x58, 0xd8, 0x76, 0x1a, 0x66, 0xe7, 0x7b, 0xdd, 0xf2, 0xd2, 0x66, 0x9c, 0x1c, 0xee, 0xe7, 0x80, + 0x3e, 0xa7, 0xc1, 0xf9, 0xba, 0x63, 0x7b, 0xac, 0xb4, 0xfb, 0x88, 0x5c, 0x73, 0x5d, 0xc7, 0xe5, + 0xbc, 0x67, 0x4e, 0xc9, 0x9b, 0x05, 0xe2, 0xeb, 0x49, 0x24, 0x71, 0x32, 0x27, 0xf4, 0x06, 0x14, + 0xda, 0xae, 0x73, 0x64, 0x36, 0x88, 0x2b, 0x52, 0x84, 0xdb, 0x69, 0xdc, 0xaa, 0xd8, 0x15, 0x34, + 0x43, 0x4d, 0x10, 0xb4, 0x60, 0xc9, 0x4f, 0xff, 0xfa, 0x14, 0xcc, 0xab, 0xe8, 0xe8, 0xd3, 0x00, + 0x6d, 0xd7, 0x69, 0x11, 0xff, 0x90, 0xc8, 0x02, 0xa5, 0x9b, 0x93, 0xde, 0x68, 0x08, 0xe8, 0x89, + 0x0b, 0x1f, 0x4c, 0x93, 0x86, 0xad, 0x38, 0xc2, 0x11, 0xb9, 0x30, 0x7d, 0x9f, 0xdb, 0x34, 0x61, + 0xe2, 0x5f, 0x4e, 0xc5, 0x21, 0x11, 0x9c, 0x8b, 0xd4, 0xe4, 0x88, 0x26, 0x1c, 0x30, 0x42, 0xfb, + 0x90, 0x7d, 0x40, 0xf6, 0xd3, 0xa9, 0xbd, 0xbf, 0x4b, 0x44, 0xa8, 0x50, 0x9d, 0xee, 0x75, 0xcb, + 0xd9, 0xbb, 0x64, 0x1f, 0x53, 0xe2, 0x74, 0x5e, 0x0d, 0x9e, 0x7e, 0x12, 0xaa, 0x62, 0xc2, 0x79, + 0x29, 0xb9, 0x2c, 0x3e, 0x2f, 0xd1, 0x84, 0x03, 0x46, 0xe8, 0x0d, 0x98, 0x79, 0x60, 0x1c, 0x91, + 0x03, 0xd7, 0xb1, 0x7d, 0x51, 0xe0, 0x30, 0x61, 0xe1, 0xcd, 0xdd, 0x80, 0x9c, 0xe0, 0xcb, 0xac, + 0xad, 0x6c, 0xc4, 0x21, 0x3b, 0x74, 0x04, 0x05, 0x9b, 0x3c, 0xc0, 0xc4, 0x32, 0xeb, 0xa2, 0xe6, + 0x61, 0x42, 0xb1, 0xbe, 0x29, 0xa8, 0x09, 0xce, 0xcc, 0x0c, 0x05, 0x6d, 0x58, 0xf2, 0xa2, 0x6b, + 0x79, 0xcf, 0xd9, 0x17, 0x8a, 0x6a, 0xc2, 0xb5, 0x94, 0x61, 0x1f, 0x5f, 0xcb, 0x2d, 0x67, 0x1f, + 0x53, 0xe2, 0xfa, 0x37, 0x73, 0x30, 0x1b, 0xbd, 0x4b, 0x38, 0x82, 0xcd, 0x92, 0x6e, 0x53, 0x66, + 0x1c, 0xb7, 0x89, 0x7a, 0xbd, 0xad, 0xd0, 0xc6, 0x07, 0x47, 0x65, 0x9b, 0xa9, 0x79, 0x0d, 0xa1, + 0xd7, 0x1b, 0x69, 0xf4, 0xb0, 0xc2, 0x74, 0x8c, 0xdc, 0x15, 0xf5, 0x83, 0xb8, 0x39, 0xe4, 0xc5, + 0xda, 0xd2, 0x0f, 0x52, 0x0c, 0xdc, 0x55, 0x80, 0xf0, 0x56, 0xa1, 0x38, 0xc0, 0x94, 0x87, 0x57, + 0x91, 0xdb, 0x8e, 0x11, 0x2c, 0xf4, 0x34, 0x4c, 0x51, 0x83, 0x41, 0x1a, 0xa2, 0x8a, 0x5a, 0x86, + 0x16, 0xd7, 0x59, 0x2b, 0x16, 0x50, 0xf4, 0x3c, 0xb5, 0xed, 0xa1, 0x9a, 0x17, 0xc5, 0xd1, 0xcb, + 0xa1, 0x6d, 0x0f, 0x61, 0x58, 0xc1, 0xa4, 0x43, 0x27, 0x54, 0x2b, 0x33, 0xd5, 0x1f, 0x19, 0x3a, + 0x53, 0xd5, 0x98, 0xc3, 0x58, 0xa8, 0x1b, 0xd3, 0xe2, 0x4c, 0x69, 0xe7, 0x23, 0xa1, 0x6e, 0x0c, + 0x8e, 0xfb, 0x7a, 0xe8, 0x1f, 0x87, 0x79, 0x55, 0x9a, 0xe9, 0x27, 0x6e, 0xbb, 0xce, 0x81, 0x69, + 0x91, 0x78, 0x90, 0xbe, 0xcb, 0x9b, 0x71, 0x00, 0x1f, 0x2d, 0xed, 0xfc, 0x97, 0x59, 0x38, 0x77, + 0xb3, 0x69, 0xda, 0x0f, 0x63, 0x27, 0x4a, 0x49, 0x8f, 0x15, 0x68, 0xe3, 0x3e, 0x56, 0x10, 0xd6, + 0x9e, 0x89, 0xa7, 0x17, 0x92, 0x6b, 0xcf, 0x82, 0x77, 0x19, 0x54, 0x5c, 0xf4, 0x3d, 0x0d, 0x9e, + 0x34, 0x1a, 0xdc, 0xbf, 0x30, 0x2c, 0xd1, 0x1a, 0x32, 0x0d, 0x64, 0xdc, 0x9b, 0x50, 0x5b, 0xf4, + 0x4f, 0x7e, 0xb5, 0x32, 0x84, 0x2b, 0xf7, 0x9a, 0xdf, 0x23, 0x66, 0xf0, 0xe4, 0x30, 0x54, 0x3c, + 0x74, 0xf8, 0x17, 0x6f, 0xc1, 0xbb, 0x4f, 0x64, 0x34, 0x96, 0x6f, 0xfc, 0x79, 0x0d, 0x66, 0xf8, + 0xe9, 0x11, 0x26, 0x07, 0x74, 0xf3, 0x18, 0x6d, 0xf3, 0x15, 0xe2, 0x7a, 0xc1, 0x8d, 0xc3, 0x99, + 0x70, 0xf3, 0x54, 0x76, 0x37, 0x05, 0x04, 0x47, 0xb0, 0xa8, 0x7a, 0xba, 0x6f, 0xda, 0x0d, 0xb1, + 0x4c, 0x52, 0x3d, 0xbd, 0x6c, 0xda, 0x0d, 0xcc, 0x20, 0x52, 0x81, 0x65, 0x07, 0x29, 0x30, 0xfd, + 0x0f, 0x34, 0x98, 0x67, 0xa5, 0xa5, 0xa1, 0x73, 0xf8, 0x9c, 0x4c, 0xd5, 0xf1, 0x61, 0x5c, 0x52, + 0x53, 0x75, 0xef, 0x74, 0xcb, 0x45, 0x5e, 0x8c, 0xaa, 0x66, 0xee, 0x5e, 0x13, 0x01, 0x1e, 0x4b, + 0x28, 0x66, 0xc6, 0x8e, 0x3f, 0xe4, 0x71, 0x46, 0x2d, 0x20, 0x82, 0x43, 0x7a, 0xfa, 0xd7, 0xb3, + 0x70, 0x2e, 0xa1, 0x46, 0x8a, 0xc6, 0x5e, 0x53, 0x96, 0xb1, 0x4f, 0xac, 0x20, 0x1d, 0xf6, 0x7a, + 0xea, 0x75, 0x58, 0xab, 0xdb, 0x8c, 0x3e, 0x97, 0x24, 0xa9, 0x9f, 0x78, 0x23, 0x16, 0xcc, 0xd1, + 0x6f, 0x69, 0x50, 0x34, 0x22, 0xc2, 0xce, 0x33, 0x84, 0xfb, 0xe9, 0x0f, 0xa6, 0x4f, 0xb6, 0x23, + 0x95, 0x0d, 0xa1, 0x28, 0x47, 0xc7, 0x72, 0xf1, 0xe7, 0xa1, 0x18, 0x99, 0xc2, 0x38, 0x32, 0x7a, + 0xf1, 0x45, 0x58, 0x9c, 0x48, 0xc6, 0x3f, 0x02, 0xe3, 0x5e, 0x61, 0xa5, 0x16, 0xe1, 0x41, 0xb4, + 0xe2, 0x5a, 0x7e, 0x71, 0x51, 0x72, 0x2d, 0xa0, 0xfa, 0x3e, 0x2c, 0xc6, 0x1d, 0xd0, 0x71, 0xce, + 0x44, 0x47, 0x52, 0xb7, 0x1f, 0x80, 0x31, 0x2f, 0x9d, 0xea, 0x7f, 0x95, 0x81, 0x69, 0x51, 0x68, + 0xf9, 0x08, 0x8a, 0x82, 0xee, 0x2b, 0xa7, 0xca, 0x9b, 0xa9, 0xd4, 0x87, 0x0e, 0xac, 0x08, 0xf2, + 0x62, 0x15, 0x41, 0x2f, 0xa7, 0xc3, 0x6e, 0x78, 0x39, 0xd0, 0x57, 0x33, 0xb0, 0x10, 0x2b, 0x5c, + 0x45, 0x5f, 0xd4, 0xfa, 0xb3, 0xe0, 0x77, 0x52, 0xad, 0x8d, 0x95, 0x25, 0x67, 0xc3, 0x13, 0xe2, + 0x9e, 0x72, 0x8d, 0xfd, 0x76, 0x6a, 0x4f, 0x9d, 0x0c, 0xbd, 0xd1, 0xfe, 0x4f, 0x1a, 0x3c, 0x3e, + 0xb0, 0x94, 0x97, 0x5d, 0x17, 0x72, 0x55, 0xa8, 0x90, 0xbd, 0x94, 0x4b, 0xf3, 0xe5, 0x69, 0x66, + 0xfc, 0x86, 0x47, 0x9c, 0x3d, 0x7a, 0x16, 0x66, 0x99, 0x1e, 0xa7, 0xdb, 0xc7, 0x27, 0x6d, 0xf1, + 0x46, 0x05, 0x3b, 0x39, 0xa8, 0x45, 0xda, 0xb1, 0x82, 0xa5, 0xff, 0x9e, 0x06, 0xa5, 0x41, 0x97, + 0x47, 0x46, 0xf0, 0xcb, 0x7f, 0x2e, 0x56, 0xa0, 0x53, 0xee, 0x2b, 0xd0, 0x89, 0x79, 0xe6, 0x41, + 0x2d, 0x4e, 0xc4, 0x29, 0xce, 0x9e, 0x50, 0x7f, 0xf2, 0x15, 0x0d, 0x2e, 0x0c, 0x10, 0x9c, 0xbe, + 0x42, 0x2d, 0xed, 0xd4, 0x85, 0x5a, 0x99, 0x51, 0x0b, 0xb5, 0xf4, 0xbf, 0xc9, 0xc2, 0xa2, 0x18, + 0x4f, 0x68, 0xcc, 0x9f, 0x57, 0xca, 0x9c, 0xde, 0x13, 0x2b, 0x73, 0x5a, 0x8e, 0xe3, 0xff, 0x5f, + 0x8d, 0xd3, 0x4f, 0x57, 0x8d, 0xd3, 0x4f, 0x32, 0x70, 0x3e, 0xf1, 0x62, 0x0e, 0xfa, 0x52, 0x82, + 0x16, 0xbc, 0x9b, 0xf2, 0x0d, 0xa0, 0x11, 0xf5, 0xe0, 0xa4, 0x85, 0x41, 0xbf, 0x19, 0x2d, 0xc8, + 0xe1, 0x61, 0xc2, 0xc1, 0x19, 0xdc, 0x65, 0x1a, 0xb3, 0x36, 0x47, 0xff, 0xd5, 0x2c, 0x5c, 0x19, + 0x95, 0xd0, 0x4f, 0x69, 0xed, 0xa6, 0xa7, 0xd4, 0x6e, 0x3e, 0x1a, 0x0b, 0x75, 0x36, 0x65, 0x9c, + 0x5f, 0xcb, 0x4a, 0xb3, 0xd7, 0x2f, 0x9f, 0x23, 0x25, 0x17, 0xa6, 0xa9, 0x17, 0x13, 0x3c, 0x4b, + 0x11, 0xaa, 0xc2, 0xe9, 0x1a, 0x6f, 0x7e, 0xa7, 0x5b, 0x5e, 0x12, 0xb7, 0xdf, 0x6b, 0xc4, 0x17, + 0x8d, 0x38, 0xe8, 0x84, 0xae, 0x40, 0xc1, 0xe5, 0xd0, 0xa0, 0x5a, 0x4d, 0x24, 0x4c, 0x78, 0x1b, + 0x96, 0x50, 0xf4, 0x99, 0x88, 0xdb, 0x97, 0x3b, 0xab, 0xbb, 0x21, 0xc3, 0xf2, 0x40, 0xaf, 0x43, + 0xc1, 0x0b, 0xde, 0xac, 0xe0, 0xa7, 0x83, 0xcf, 0x8c, 0x58, 0x04, 0x49, 0xa3, 0x84, 0xe0, 0x01, + 0x0b, 0x3e, 0x3f, 0xf9, 0xbc, 0x85, 0x24, 0x89, 0x74, 0xe9, 0xa0, 0xf3, 0x23, 0x1e, 0x48, 0x70, + 0xce, 0xdf, 0xd6, 0xa0, 0x28, 0x56, 0xeb, 0x11, 0xd4, 0x65, 0xde, 0x53, 0xeb, 0x32, 0xaf, 0xa5, + 0xa2, 0x3b, 0x06, 0x14, 0x65, 0xde, 0x83, 0xd9, 0xe8, 0xdd, 0x4c, 0xf4, 0x6a, 0x44, 0xf7, 0x69, + 0x93, 0xdc, 0x01, 0x0b, 0xb4, 0x63, 0xa8, 0x17, 0xf5, 0xb7, 0xa6, 0xe5, 0x57, 0x64, 0xd5, 0x9f, + 0x51, 0x19, 0xd4, 0x86, 0xca, 0x60, 0x54, 0x04, 0x32, 0xe9, 0x8b, 0xc0, 0x6d, 0x28, 0x04, 0x0a, + 0x4a, 0x98, 0xf1, 0xa7, 0xa2, 0x15, 0x2d, 0xd4, 0x17, 0xa0, 0xc4, 0x22, 0x82, 0xcb, 0xa2, 0x0a, + 0xb9, 0x86, 0x52, 0x71, 0x4a, 0x32, 0xe8, 0x0d, 0x28, 0x3e, 0x70, 0xdc, 0xfb, 0x96, 0x63, 0xb0, + 0xa7, 0x63, 0x20, 0x8d, 0x73, 0x5e, 0x79, 0xba, 0xc2, 0x2b, 0xf9, 0xee, 0x86, 0xf4, 0x71, 0x94, + 0x19, 0xaa, 0xc0, 0x42, 0xcb, 0xb4, 0x31, 0x31, 0x1a, 0xb2, 0xfc, 0x32, 0xc7, 0x9f, 0xcb, 0x08, + 0x9c, 0xdc, 0x1d, 0x15, 0x8c, 0xe3, 0xf8, 0xe8, 0x93, 0x50, 0xf0, 0xc4, 0xfd, 0xcf, 0x74, 0x4e, + 0xe4, 0x65, 0x78, 0xc4, 0x89, 0x86, 0xdf, 0x2e, 0x68, 0xc1, 0x92, 0x21, 0xda, 0x86, 0x65, 0x57, + 0xdc, 0xb0, 0x52, 0x5e, 0x83, 0xe3, 0xfb, 0x93, 0xbd, 0xca, 0x80, 0x13, 0xe0, 0x38, 0xb1, 0x17, + 0xf5, 0x62, 0xd8, 0x25, 0x63, 0x7e, 0x24, 0x5b, 0x08, 0xbd, 0x18, 0x26, 0xf0, 0x0d, 0x2c, 0xa0, + 0xc3, 0xca, 0x79, 0x0b, 0x13, 0x94, 0xf3, 0xde, 0x85, 0x19, 0x97, 0xb0, 0x50, 0xa0, 0x12, 0xa4, + 0xeb, 0xc6, 0xae, 0x13, 0xc0, 0x01, 0x01, 0x1c, 0xd2, 0xa2, 0x4b, 0x64, 0xa8, 0x8f, 0x3b, 0xdc, + 0x4e, 0xf1, 0x8d, 0x51, 0xb1, 0x4c, 0x03, 0xae, 0x52, 0xea, 0xff, 0x39, 0x07, 0x73, 0x4a, 0xc4, + 0x8b, 0x9e, 0x82, 0x3c, 0xbb, 0xc3, 0xc6, 0x76, 0x72, 0x21, 0xd4, 0x36, 0xec, 0xd2, 0x1b, 0xe6, + 0x30, 0xf4, 0x55, 0x0d, 0x16, 0xda, 0xca, 0xe9, 0x5c, 0xa0, 0xe4, 0x26, 0xcc, 0xba, 0xa8, 0x47, + 0x7e, 0x91, 0x67, 0x91, 0x54, 0x66, 0x38, 0xce, 0x9d, 0xee, 0x15, 0x51, 0x3a, 0x63, 0x11, 0x97, + 0x61, 0x0b, 0x77, 0x44, 0x92, 0x58, 0x57, 0xc1, 0x38, 0x8e, 0x4f, 0x57, 0x98, 0xcd, 0x6e, 0x92, + 0x17, 0x19, 0x2b, 0x01, 0x01, 0x1c, 0xd2, 0x42, 0x2f, 0xc2, 0xbc, 0xb8, 0xd3, 0xbf, 0xeb, 0x34, + 0x6e, 0x18, 0xde, 0xa1, 0xf0, 0xc3, 0x65, 0xdc, 0xb0, 0xae, 0x40, 0x71, 0x0c, 0x9b, 0xcd, 0x2d, + 0x7c, 0x38, 0x81, 0x11, 0x98, 0x52, 0x5f, 0x8d, 0x5a, 0x57, 0xc1, 0x38, 0x8e, 0x8f, 0xde, 0x17, + 0x51, 0xd1, 0x3c, 0xa3, 0x21, 0x37, 0x6e, 0x82, 0x9a, 0xae, 0xc0, 0x42, 0x87, 0x85, 0x2d, 0x8d, + 0x00, 0x28, 0xb6, 0x8e, 0x64, 0x78, 0x47, 0x05, 0xe3, 0x38, 0x3e, 0x7a, 0x01, 0xe6, 0x5c, 0xaa, + 0x88, 0x24, 0x01, 0x9e, 0xe6, 0x90, 0x67, 0xf6, 0x38, 0x0a, 0xc4, 0x2a, 0x2e, 0x7a, 0x09, 0x96, + 0xc2, 0xdb, 0xcd, 0x01, 0x01, 0x9e, 0xf7, 0x90, 0x0f, 0x27, 0x54, 0xe2, 0x08, 0xb8, 0xbf, 0x0f, + 0xfa, 0x05, 0x58, 0x8c, 0x7c, 0x89, 0x4d, 0xbb, 0x41, 0x1e, 0x8a, 0x1b, 0xa8, 0xcb, 0x2c, 0x77, + 0x12, 0x83, 0xe1, 0x3e, 0x6c, 0xf4, 0x21, 0x98, 0xaf, 0x3b, 0x96, 0xc5, 0xd4, 0x11, 0x7f, 0xb1, + 0x88, 0x5f, 0x35, 0xe5, 0x97, 0x72, 0x15, 0x08, 0x8e, 0x61, 0xa2, 0x2d, 0x40, 0xce, 0xbe, 0x47, + 0xdc, 0x23, 0xd2, 0x78, 0x89, 0x3f, 0x3a, 0x4d, 0xad, 0xf1, 0x9c, 0x5a, 0xb8, 0x77, 0xab, 0x0f, + 0x03, 0x27, 0xf4, 0x42, 0xfb, 0x70, 0x31, 0x30, 0x0d, 0xfd, 0x3d, 0x4a, 0x25, 0x25, 0xba, 0xb9, + 0x78, 0x77, 0x20, 0x26, 0x1e, 0x42, 0x05, 0xfd, 0x92, 0x5a, 0x39, 0x3e, 0x9f, 0xc6, 0x1b, 0x90, + 0xf1, 0x40, 0xfe, 0xc4, 0xb2, 0x71, 0x17, 0xa6, 0x78, 0xad, 0x66, 0x69, 0x21, 0x8d, 0x5b, 0xdd, + 0xd1, 0x07, 0x52, 0x42, 0x93, 0xc1, 0x5b, 0xb1, 0xe0, 0x84, 0x3e, 0x0d, 0x33, 0xfb, 0xc1, 0x6b, + 0x59, 0xa5, 0xc5, 0x34, 0xcc, 0x64, 0xec, 0xe1, 0xb7, 0x30, 0x50, 0x95, 0x00, 0x1c, 0xb2, 0x44, + 0x4f, 0x43, 0xf1, 0xc6, 0x6e, 0x45, 0x4a, 0xfa, 0x12, 0x93, 0xb0, 0x1c, 0xed, 0x82, 0xa3, 0x00, + 0xba, 0x8b, 0xa5, 0xfb, 0x84, 0xd8, 0x92, 0x87, 0xe6, 0xb7, 0xdf, 0x1b, 0xa2, 0xd8, 0x2c, 0x15, + 0x86, 0x6b, 0xa5, 0x73, 0x31, 0x6c, 0xd1, 0x8e, 0x25, 0x06, 0x7a, 0x1d, 0x8a, 0xc2, 0x26, 0x31, + 0xfd, 0xb7, 0x7c, 0xba, 0x5b, 0x09, 0x38, 0x24, 0x81, 0xa3, 0xf4, 0xd0, 0x73, 0x50, 0x6c, 0xb3, + 0x47, 0x84, 0xc8, 0xf5, 0x8e, 0x65, 0x95, 0xce, 0x33, 0xdd, 0x2c, 0x73, 0x04, 0xbb, 0x21, 0x08, + 0x47, 0xf1, 0xd0, 0x33, 0x41, 0x1e, 0xfb, 0x5d, 0x4a, 0xca, 0x47, 0xe6, 0xb1, 0xa5, 0xd3, 0x3b, + 0xa0, 0xfa, 0xef, 0xc2, 0x09, 0xe7, 0x18, 0x9f, 0x0b, 0xcf, 0x71, 0xe5, 0x3b, 0x19, 0x9f, 0x8a, + 0x4a, 0x83, 0x96, 0xc6, 0xd3, 0xd8, 0x7d, 0x4f, 0xb1, 0x71, 0x63, 0x91, 0x28, 0x0b, 0x6d, 0x29, + 0xff, 0xa9, 0x5c, 0xa9, 0x55, 0xdf, 0x00, 0xe1, 0x41, 0x91, 0x2a, 0xfd, 0xfa, 0xf7, 0x73, 0xf2, + 0x2c, 0x27, 0x96, 0xbe, 0x75, 0x21, 0x6f, 0x7a, 0xbe, 0xe9, 0xa4, 0x78, 0x0d, 0x20, 0xf6, 0x78, + 0x06, 0x2b, 0x47, 0x63, 0x00, 0xcc, 0x59, 0x51, 0x9e, 0x76, 0xd3, 0xb4, 0x1f, 0x8a, 0xe9, 0xdf, + 0x4e, 0x3d, 0x2f, 0xcb, 0x79, 0x32, 0x00, 0xe6, 0xac, 0xd0, 0x3d, 0xc8, 0x1a, 0xd6, 0x7e, 0x4a, + 0xcf, 0xa0, 0xc7, 0xff, 0x95, 0x00, 0x2f, 0xe6, 0xa8, 0x6c, 0x57, 0x31, 0x65, 0x42, 0x79, 0x79, + 0x2d, 0x53, 0xf8, 0x17, 0x13, 0xf2, 0xaa, 0xed, 0x6c, 0x26, 0xf1, 0xaa, 0xed, 0x6c, 0x62, 0xca, + 0x04, 0x7d, 0x51, 0x03, 0x30, 0xe4, 0x33, 0xff, 0xe9, 0xbc, 0x5b, 0x38, 0xe8, 0xdf, 0x06, 0xf0, + 0x2a, 0xab, 0x10, 0x8a, 0x23, 0x9c, 0xf5, 0x37, 0x35, 0x58, 0xea, 0x1b, 0x6c, 0xfc, 0x3f, 0x20, + 0x68, 0xa3, 0xff, 0x07, 0x04, 0xf1, 0xbc, 0x4a, 0xad, 0x6d, 0x99, 0x89, 0x57, 0x69, 0xf6, 0x62, + 0x70, 0xdc, 0xd7, 0x43, 0xff, 0x96, 0x06, 0xc5, 0x48, 0x19, 0x34, 0xf5, 0x7b, 0x59, 0xb9, 0xb8, + 0x18, 0x46, 0xf8, 0xb2, 0x0c, 0x3b, 0xbe, 0xe2, 0x30, 0x7e, 0x92, 0xda, 0x0c, 0xcf, 0x13, 0x23, + 0x27, 0xa9, 0xb4, 0x15, 0x0b, 0x28, 0xba, 0x0c, 0x39, 0xcf, 0x27, 0x6d, 0x26, 0x51, 0x91, 0xaa, + 0x68, 0x96, 0x4f, 0x60, 0x10, 0xc6, 0x8e, 0x2a, 0x47, 0x51, 0xe2, 0x12, 0x79, 0xc8, 0xc6, 0xa0, + 0x6e, 0x36, 0x83, 0xa1, 0x4b, 0x90, 0x25, 0x76, 0x43, 0x78, 0x8b, 0x45, 0x81, 0x92, 0xbd, 0x66, + 0x37, 0x30, 0x6d, 0xd7, 0x6f, 0xc1, 0x6c, 0x8d, 0xd4, 0x5d, 0xe2, 0xbf, 0x4c, 0x8e, 0x47, 0x3b, + 0xeb, 0xbb, 0xc4, 0x73, 0xa4, 0x19, 0x95, 0x20, 0xed, 0x4e, 0xdb, 0xf5, 0x3f, 0xd2, 0x20, 0xf6, + 0xae, 0x50, 0xe4, 0x54, 0x45, 0x1b, 0x74, 0xaa, 0xa2, 0xc4, 0xff, 0x99, 0xa1, 0xf1, 0xff, 0x16, + 0xa0, 0x96, 0xe1, 0xd7, 0x0f, 0xc5, 0xfa, 0x88, 0x27, 0xac, 0xb8, 0xa3, 0x1e, 0x5e, 0xba, 0xe8, + 0xc3, 0xc0, 0x09, 0xbd, 0xf4, 0x25, 0x58, 0x90, 0x51, 0x3c, 0x97, 0x0c, 0xfd, 0xdb, 0x59, 0x98, + 0x55, 0x5e, 0x90, 0x3e, 0xf9, 0x8b, 0x8c, 0x3e, 0xf6, 0x84, 0x68, 0x3c, 0x3b, 0x66, 0x34, 0x1e, + 0x3d, 0xfe, 0xc8, 0x9d, 0xed, 0xf1, 0x47, 0x3e, 0x9d, 0xe3, 0x0f, 0x1f, 0xa6, 0xc5, 0xff, 0x2c, + 0x11, 0x55, 0x75, 0x3b, 0x29, 0x5d, 0x9b, 0x14, 0xd7, 0xc0, 0x58, 0x21, 0x61, 0xb0, 0xcb, 0x03, + 0x56, 0xfa, 0x37, 0xf2, 0x30, 0xaf, 0x5e, 0xa4, 0x1c, 0x61, 0x25, 0xdf, 0xd7, 0xb7, 0x92, 0x63, + 0x86, 0x38, 0xd9, 0x49, 0x43, 0x9c, 0xdc, 0xa4, 0x21, 0x4e, 0xfe, 0x14, 0x21, 0x4e, 0x7f, 0x80, + 0x32, 0x35, 0x72, 0x80, 0xf2, 0x61, 0x99, 0x4a, 0x9b, 0x56, 0xce, 0x9e, 0xc3, 0x54, 0x1a, 0x52, + 0x97, 0x61, 0xdd, 0x69, 0x24, 0xa6, 0x24, 0x0b, 0x27, 0xd4, 0xe9, 0xb9, 0x89, 0x99, 0xaf, 0xf1, + 0x4f, 0x51, 0xde, 0x35, 0x46, 0xd6, 0x2b, 0xfc, 0xb7, 0x3c, 0xcc, 0x42, 0x80, 0x6a, 0x5d, 0x6a, + 0x21, 0x08, 0x47, 0xf1, 0xd8, 0x13, 0xcd, 0xea, 0x03, 0xd2, 0x2c, 0x62, 0x8c, 0x3e, 0xd1, 0x1c, + 0x7b, 0x70, 0x3a, 0x8e, 0xaf, 0x7f, 0x36, 0x03, 0xe1, 0x23, 0xd8, 0xec, 0xb5, 0x2a, 0x2f, 0xa2, + 0xa6, 0x85, 0x33, 0xb5, 0x35, 0xe9, 0x93, 0x70, 0x21, 0x45, 0x91, 0xb4, 0x8e, 0xb4, 0x60, 0x85, + 0xe3, 0x7f, 0xc3, 0xe3, 0xd7, 0x06, 0x2c, 0xc4, 0x6a, 0x77, 0x53, 0x2f, 0x82, 0xf9, 0x56, 0x06, + 0x66, 0x64, 0xf5, 0x33, 0xb5, 0x6c, 0x1d, 0x37, 0x78, 0x58, 0x47, 0x5a, 0xb6, 0x3b, 0x78, 0x1b, + 0xd3, 0x76, 0xf4, 0x10, 0xa6, 0x0f, 0x89, 0xd1, 0x20, 0x6e, 0x70, 0x4e, 0xb5, 0x93, 0x52, 0xd9, + 0xf5, 0x0d, 0x46, 0x35, 0x9c, 0x0b, 0xff, 0xed, 0xe1, 0x80, 0x1d, 0x7a, 0x11, 0xe6, 0x7d, 0xb3, + 0x45, 0x68, 0x80, 0x11, 0xb1, 0x1a, 0xd9, 0xf0, 0xf0, 0x67, 0x4f, 0x81, 0xe2, 0x18, 0x36, 0x55, + 0x6b, 0xf7, 0x3c, 0xc7, 0x66, 0x77, 0x94, 0x73, 0x6a, 0x14, 0xb7, 0x55, 0xbb, 0x75, 0x93, 0x5d, + 0x51, 0x96, 0x18, 0x14, 0xdb, 0x64, 0xd5, 0x9f, 0x2e, 0x11, 0x69, 0xad, 0xc5, 0xf0, 0xae, 0x0a, + 0x6f, 0xc7, 0x12, 0x43, 0xbf, 0x03, 0x0b, 0xb1, 0x89, 0x04, 0x1e, 0x82, 0x96, 0xec, 0x21, 0x8c, + 0xf4, 0xbf, 0x85, 0xaa, 0xab, 0x6f, 0xfd, 0x60, 0xe5, 0xb1, 0xef, 0xfc, 0x60, 0xe5, 0xb1, 0xef, + 0xfe, 0x60, 0xe5, 0xb1, 0xcf, 0xf6, 0x56, 0xb4, 0xb7, 0x7a, 0x2b, 0xda, 0x77, 0x7a, 0x2b, 0xda, + 0x77, 0x7b, 0x2b, 0xda, 0xf7, 0x7b, 0x2b, 0xda, 0x9b, 0x3f, 0x5c, 0x79, 0xec, 0xd5, 0x42, 0xf0, + 0x31, 0xff, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x3e, 0x5d, 0xf5, 0x43, 0x5a, 0x6d, 0x00, 0x00, } func (m *ALBTrafficRouting) Marshal() (dAtA []byte, err error) { @@ -5747,6 +5747,11 @@ func (m *RolloutExperimentTemplate) MarshalToSizedBuffer(dAtA []byte) (int, erro _ = i var l int _ = l + if m.Weight != nil { + i = encodeVarintGenerated(dAtA, i, uint64(*m.Weight)) + i-- + dAtA[i] = 0x30 + } if m.Selector != nil { { size, err := m.Selector.MarshalToSizedBuffer(dAtA[:i]) @@ -7986,6 +7991,9 @@ func (m *RolloutExperimentTemplate) Size() (n int) { l = m.Selector.Size() n += 1 + l + sovGenerated(uint64(l)) } + if m.Weight != nil { + n += 1 + sovGenerated(uint64(*m.Weight)) + } return n } @@ -9251,6 +9259,7 @@ func (this *RolloutExperimentTemplate) String() string { `Replicas:` + valueToStringGenerated(this.Replicas) + `,`, `Metadata:` + strings.Replace(strings.Replace(this.Metadata.String(), "PodTemplateMetadata", "PodTemplateMetadata", 1), `&`, ``, 1) + `,`, `Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`, + `Weight:` + valueToStringGenerated(this.Weight) + `,`, `}`, }, "") return s @@ -19529,6 +19538,26 @@ func (m *RolloutExperimentTemplate) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Weight", 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.Weight = &v default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/pkg/apis/rollouts/v1alpha1/generated.proto b/pkg/apis/rollouts/v1alpha1/generated.proto index 368d315dc5..6cf6fd21e0 100644 --- a/pkg/apis/rollouts/v1alpha1/generated.proto +++ b/pkg/apis/rollouts/v1alpha1/generated.proto @@ -961,6 +961,9 @@ message RolloutExperimentTemplate { // use the same selector as the Rollout // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 5; + + // Weight sets the percentage of traffic the template's replicas should receive + optional int32 weight = 6; } // RolloutList is a list of Rollout resources diff --git a/pkg/apis/rollouts/v1alpha1/openapi_generated.go b/pkg/apis/rollouts/v1alpha1/openapi_generated.go index 1c990ed785..5962af0c1c 100644 --- a/pkg/apis/rollouts/v1alpha1/openapi_generated.go +++ b/pkg/apis/rollouts/v1alpha1/openapi_generated.go @@ -2865,6 +2865,13 @@ func schema_pkg_apis_rollouts_v1alpha1_RolloutExperimentTemplate(ref common.Refe Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), }, }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "Weight sets the percentage of traffic the template's replicas should receive", + Type: []string{"integer"}, + Format: "int32", + }, + }, }, Required: []string{"name", "specRef"}, }, diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index 230b9f9d06..930dec7473 100644 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -449,6 +449,8 @@ type RolloutExperimentTemplate struct { // use the same selector as the Rollout // +optional Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,5,opt,name=selector"` + // Weight sets the percentage of traffic the template's replicas should receive + Weight *int32 `json:"weight,omitempty" protobuf:"varint,6,opt,name=weight"` } // PodTemplateMetadata extra labels to add to the template diff --git a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go index 5f45a8e837..7d7c9ebbf6 100644 --- a/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/rollouts/v1alpha1/zz_generated.deepcopy.go @@ -1553,6 +1553,11 @@ func (in *RolloutExperimentTemplate) DeepCopyInto(out *RolloutExperimentTemplate *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } + if in.Weight != nil { + in, out := &in.Weight, &out.Weight + *out = new(int32) + **out = **in + } return } diff --git a/rollout/controller.go b/rollout/controller.go index 7e85b00937..fd1b55099c 100644 --- a/rollout/controller.go +++ b/rollout/controller.go @@ -40,6 +40,7 @@ import ( clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" informers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions/rollouts/v1alpha1" listers "github.com/argoproj/argo-rollouts/pkg/client/listers/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/istio" analysisutil "github.com/argoproj/argo-rollouts/utils/analysis" @@ -138,9 +139,9 @@ type reconcilerBase struct { podRestarter RolloutPodRestarter // used for unit testing - enqueueRollout func(obj interface{}) //nolint:structcheck - enqueueRolloutAfter func(obj interface{}, duration time.Duration) //nolint:structcheck - newTrafficRoutingReconciler func(roCtx *rolloutContext) (TrafficRoutingReconciler, error) //nolint:structcheck + enqueueRollout func(obj interface{}) //nolint:structcheck + enqueueRolloutAfter func(obj interface{}, duration time.Duration) //nolint:structcheck + newTrafficRoutingReconciler func(roCtx *rolloutContext) (trafficrouting.TrafficRoutingReconciler, error) //nolint:structcheck // recorder is an event recorder for recording Event resources to the Kubernetes API. recorder record.EventRecorder diff --git a/rollout/controller_test.go b/rollout/controller_test.go index e4c3cf915a..8f9b7d346c 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -46,6 +46,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake" informers "github.com/argoproj/argo-rollouts/pkg/client/informers/externalversions" "github.com/argoproj/argo-rollouts/rollout/mocks" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/annotations" "github.com/argoproj/argo-rollouts/utils/conditions" "github.com/argoproj/argo-rollouts/utils/defaults" @@ -539,7 +540,7 @@ func (f *fixture) newController(resync resyncFunc) (*Controller, informers.Share c.enqueueRollout(obj) } - c.newTrafficRoutingReconciler = func(roCtx *rolloutContext) (TrafficRoutingReconciler, error) { + c.newTrafficRoutingReconciler = func(roCtx *rolloutContext) (trafficrouting.TrafficRoutingReconciler, error) { return f.fakeTrafficRouting, nil } diff --git a/rollout/experiment.go b/rollout/experiment.go index a8c8f06f5d..c64e0c41da 100644 --- a/rollout/experiment.go +++ b/rollout/experiment.go @@ -63,6 +63,9 @@ func GetExperimentFromTemplate(r *v1alpha1.Rollout, stableRS, newRS *appsv1.Repl Name: templateStep.Name, Replicas: templateStep.Replicas, } + if templateStep.Weight != nil { + template.Service = &v1alpha1.TemplateService{} + } templateRS := &appsv1.ReplicaSet{} switch templateStep.SpecRef { case v1alpha1.CanarySpecRef: diff --git a/rollout/experiment_test.go b/rollout/experiment_test.go index 18277d3640..d0ae6dd0a9 100644 --- a/rollout/experiment_test.go +++ b/rollout/experiment_test.go @@ -750,3 +750,46 @@ func TestRolloutCreateExperimentWithInstanceID(t *testing.T) { assert.Equal(t, createdEx.Name, ex.Name) assert.Equal(t, "instance-id-test", createdEx.Labels[v1alpha1.LabelKeyControllerInstanceID]) } + +// TestRolloutCreateExperimentWithService verifies the controller sets CreateService for Experiment Template as expected. +// CreateService is true when Weight is set in RolloutExperimentStep for template, otherwise false. +func TestRolloutCreateExperimentWithService(t *testing.T) { + steps := []v1alpha1.CanaryStep{{ + Experiment: &v1alpha1.RolloutExperimentStep{ + Templates: []v1alpha1.RolloutExperimentTemplate{ + // Service should be created for "stable-template" + { + Name: "stable-template", + SpecRef: v1alpha1.StableSpecRef, + Replicas: pointer.Int32Ptr(1), + Weight: pointer.Int32Ptr(5), + }, + // Service should NOT be created for "canary-template" + { + Name: "canary-template", + SpecRef: v1alpha1.CanarySpecRef, + Replicas: pointer.Int32Ptr(1), + }, + }, + }, + }} + + r1 := newCanaryRollout("foo", 1, nil, steps, pointer.Int32Ptr(0), intstr.FromInt(0), intstr.FromInt(1)) + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 1, 1) + rs2 := newReplicaSetWithStatus(r2, 1, 1) + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + r2.Status.CurrentStepIndex = pointer.Int32Ptr(0) + r2.Status.StableRS = rs1PodHash + + ex, err := GetExperimentFromTemplate(r2, rs1, rs2) + assert.Nil(t, err) + + assert.Equal(t, "stable-template", ex.Spec.Templates[0].Name) + assert.NotNil(t, ex.Spec.Templates[0].Service) + + assert.Equal(t, "canary-template", ex.Spec.Templates[1].Name) + assert.Nil(t, ex.Spec.Templates[1].Service) +} diff --git a/rollout/mocks/TrafficRoutingReconciler.go b/rollout/mocks/TrafficRoutingReconciler.go index 8bc34ac700..12007d1436 100644 --- a/rollout/mocks/TrafficRoutingReconciler.go +++ b/rollout/mocks/TrafficRoutingReconciler.go @@ -2,20 +2,30 @@ package mocks -import mock "github.com/stretchr/testify/mock" +import ( + trafficrouting "github.com/argoproj/argo-rollouts/rollout/trafficrouting" + mock "github.com/stretchr/testify/mock" +) // TrafficRoutingReconciler is an autogenerated mock type for the TrafficRoutingReconciler type type TrafficRoutingReconciler struct { mock.Mock } -// SetWeight provides a mock function with given fields: desiredWeight -func (_m *TrafficRoutingReconciler) SetWeight(desiredWeight int32) error { - ret := _m.Called(desiredWeight) +// SetWeight provides a mock function with given fields: desiredWeight, additionalDestinations +func (_m *TrafficRoutingReconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { + _va := make([]interface{}, len(additionalDestinations)) + for _i := range additionalDestinations { + _va[_i] = additionalDestinations[_i] + } + var _ca []interface{} + _ca = append(_ca, desiredWeight) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 error - if rf, ok := ret.Get(0).(func(int32) error); ok { - r0 = rf(desiredWeight) + if rf, ok := ret.Get(0).(func(int32, ...trafficrouting.WeightDestination) error); ok { + r0 = rf(desiredWeight, additionalDestinations...) } else { r0 = ret.Error(0) } diff --git a/rollout/trafficrouting.go b/rollout/trafficrouting.go index 9b79d2c761..a6444b1ce5 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -10,24 +10,13 @@ import ( "github.com/argoproj/argo-rollouts/rollout/trafficrouting/nginx" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/smi" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" ) -// TrafficRoutingReconciler common function across all TrafficRouting implementation -type TrafficRoutingReconciler interface { - // UpdateHash informs a traffic routing reconciler about new canary/stable pod hashes - UpdateHash(canaryHash, stableHash string) error - // SetWeight sets the canary weight to the desired weight - SetWeight(desiredWeight int32) error - // VerifyWeight returns true if the canary is at the desired weight - VerifyWeight(desiredWeight int32) (bool, error) - // Type returns the type of the traffic routing reconciler - Type() string -} - // NewTrafficRoutingReconciler identifies return the TrafficRouting Plugin that the rollout wants to modify -func (c *Controller) NewTrafficRoutingReconciler(roCtx *rolloutContext) (TrafficRoutingReconciler, error) { +func (c *Controller) NewTrafficRoutingReconciler(roCtx *rolloutContext) (trafficrouting.TrafficRoutingReconciler, error) { rollout := roCtx.rollout if rollout.Spec.Strategy.Canary.TrafficRouting == nil { return nil, nil @@ -96,6 +85,7 @@ func (c *rolloutContext) reconcileTrafficRouting() error { currentStep, index := replicasetutil.GetCurrentCanaryStep(c.rollout) desiredWeight := int32(0) + weightDestinations := make([]trafficrouting.WeightDestination, 0) if c.rollout.Status.StableRS == c.rollout.Status.CurrentPodHash { // when we are fully promoted. desired canary weight should be 0 } else if c.pauseContext.IsAborted() { @@ -122,9 +112,31 @@ func (c *rolloutContext) reconcileTrafficRouting() error { // last setWeight step, which is set by GetCurrentSetWeight. desiredWeight = replicasetutil.GetCurrentSetWeight(c.rollout) } + + // Checks for experiment step + // If current experiment exists, then create WeightDestinations for each experiment template + exStep := replicasetutil.GetCurrentExperimentStep(c.rollout) + if exStep != nil && c.currentEx != nil && c.currentEx.Status.Phase == v1alpha1.AnalysisPhaseRunning { + getTemplateWeight := func(name string) *int32 { + for _, tmpl := range exStep.Templates { + if tmpl.Name == name { + return tmpl.Weight + } + } + return nil + } + for _, templateStatus := range c.currentEx.Status.TemplateStatuses { + templateWeight := getTemplateWeight(templateStatus.Name) + weightDestinations = append(weightDestinations, trafficrouting.WeightDestination{ + ServiceName: templateStatus.ServiceName, + PodTemplateHash: templateStatus.PodTemplateHash, + Weight: *templateWeight, + }) + } + } } - err = reconciler.SetWeight(desiredWeight) + err = reconciler.SetWeight(desiredWeight, weightDestinations...) if err != nil { c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: "TrafficRoutingError"}, err.Error()) return err diff --git a/rollout/trafficrouting/alb/alb.go b/rollout/trafficrouting/alb/alb.go index fb39f054a5..b0ea0499e2 100644 --- a/rollout/trafficrouting/alb/alb.go +++ b/rollout/trafficrouting/alb/alb.go @@ -15,6 +15,7 @@ import ( "k8s.io/utils/pointer" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/aws" "github.com/argoproj/argo-rollouts/utils/diff" ingressutil "github.com/argoproj/argo-rollouts/utils/ingress" @@ -74,7 +75,7 @@ func (r *Reconciler) Type() string { } // SetWeight modifies ALB Ingress resources to reach desired state -func (r *Reconciler) SetWeight(desiredWeight int32) error { +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { ctx := context.TODO() rollout := r.cfg.Rollout ingressName := rollout.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress diff --git a/rollout/trafficrouting/ambassador/ambassador.go b/rollout/trafficrouting/ambassador/ambassador.go index d50e7ad190..859fac0b6f 100644 --- a/rollout/trafficrouting/ambassador/ambassador.go +++ b/rollout/trafficrouting/ambassador/ambassador.go @@ -18,6 +18,7 @@ import ( "k8s.io/client-go/dynamic" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/defaults" logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" @@ -88,7 +89,7 @@ func NewReconciler(r *v1alpha1.Rollout, c ClientInterface, rec record.EventRecor // in the ambassador configuration in the traffic routing section of the rollout. If // the canary ambassador mapping is already present, it will be updated to the given // desiredWeight. -func (r *Reconciler) SetWeight(desiredWeight int32) error { +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { r.sendNormalEvent(CanaryMappingWeightUpdate, fmt.Sprintf("Set canary mapping weight to %d", desiredWeight)) ctx := context.TODO() baseMappingNameList := r.Rollout.Spec.Strategy.Canary.TrafficRouting.Ambassador.Mappings diff --git a/rollout/trafficrouting/ambassador/ambassador_test.go b/rollout/trafficrouting/ambassador/ambassador_test.go index 612a14c281..4777d109a5 100644 --- a/rollout/trafficrouting/ambassador/ambassador_test.go +++ b/rollout/trafficrouting/ambassador/ambassador_test.go @@ -7,9 +7,6 @@ import ( "sync" "testing" - "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" - "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" - "github.com/argoproj/argo-rollouts/utils/record" "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/assert" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -17,6 +14,10 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" + "github.com/argoproj/argo-rollouts/utils/record" ) const ( diff --git a/rollout/trafficrouting/istio/controller.go b/rollout/trafficrouting/istio/controller.go index b49132758c..36063846d9 100644 --- a/rollout/trafficrouting/istio/controller.go +++ b/rollout/trafficrouting/istio/controller.go @@ -5,8 +5,6 @@ import ( "fmt" "time" - "github.com/argoproj/argo-rollouts/utils/queue" - log "github.com/sirupsen/logrus" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" @@ -28,6 +26,7 @@ import ( "github.com/argoproj/argo-rollouts/utils/defaults" istioutil "github.com/argoproj/argo-rollouts/utils/istio" logutil "github.com/argoproj/argo-rollouts/utils/log" + "github.com/argoproj/argo-rollouts/utils/queue" unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" ) diff --git a/rollout/trafficrouting/istio/istio.go b/rollout/trafficrouting/istio/istio.go index c6298a43c2..5b7204bd84 100644 --- a/rollout/trafficrouting/istio/istio.go +++ b/rollout/trafficrouting/istio/istio.go @@ -16,6 +16,7 @@ import ( "k8s.io/client-go/dynamic/dynamiclister" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" evalUtils "github.com/argoproj/argo-rollouts/utils/evaluate" istioutil "github.com/argoproj/argo-rollouts/utils/istio" logutil "github.com/argoproj/argo-rollouts/utils/log" @@ -522,7 +523,7 @@ func (r *Reconciler) Type() string { } // SetWeight modifies Istio resources to reach desired state -func (r *Reconciler) SetWeight(desiredWeight int32) error { +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { ctx := context.TODO() var vsvc *unstructured.Unstructured var err error diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index ed9e95ac2b..333095560d 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -15,6 +15,7 @@ import ( extensionslisters "k8s.io/client-go/listers/extensions/v1beta1" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/argoproj/argo-rollouts/utils/diff" ingressutil "github.com/argoproj/argo-rollouts/utils/ingress" @@ -140,7 +141,7 @@ func compareCanaryIngresses(current *extensionsv1beta1.Ingress, desired *extensi } // SetWeight modifies Nginx Ingress resources to reach desired state -func (r *Reconciler) SetWeight(desiredWeight int32) error { +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { ctx := context.TODO() stableIngressName := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.Nginx.StableIngress canaryIngressName := ingressutil.GetCanaryIngressName(r.cfg.Rollout) diff --git a/rollout/trafficrouting/smi/smi.go b/rollout/trafficrouting/smi/smi.go index ee9439d564..5ce358eb6c 100644 --- a/rollout/trafficrouting/smi/smi.go +++ b/rollout/trafficrouting/smi/smi.go @@ -16,6 +16,7 @@ import ( patchtypes "k8s.io/apimachinery/pkg/types" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/argoproj/argo-rollouts/utils/diff" logutil "github.com/argoproj/argo-rollouts/utils/log" @@ -190,13 +191,13 @@ func (r *Reconciler) Type() string { } // SetWeight creates and modifies traffic splits based on the desired weight -func (r *Reconciler) SetWeight(desiredWeight int32) error { +func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { // If TrafficSplitName not set, then set to Rollout name trafficSplitName := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.SMI.TrafficSplitName if trafficSplitName == "" { trafficSplitName = r.cfg.Rollout.Name } - trafficSplits := r.generateTrafficSplits(trafficSplitName, desiredWeight) + trafficSplits := r.generateTrafficSplits(trafficSplitName, desiredWeight, additionalDestinations...) // Check if Traffic Split exists in namespace existingTrafficSplit, err := r.getTrafficSplit(trafficSplitName) @@ -228,7 +229,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32) error { return err } -func (r *Reconciler) generateTrafficSplits(trafficSplitName string, desiredWeight int32) VersionedTrafficSplits { +func (r *Reconciler) generateTrafficSplits(trafficSplitName string, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) VersionedTrafficSplits { // If root service not set, then set root service to be stable service rootSvc := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.SMI.RootService if rootSvc == "" { @@ -241,11 +242,11 @@ func (r *Reconciler) generateTrafficSplits(trafficSplitName string, desiredWeigh switch smiAPIVersion { case "v1alpha1": - trafficSplits.ts1 = trafficSplitV1Alpha1(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight) + trafficSplits.ts1 = trafficSplitV1Alpha1(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight, additionalDestinations...) case "v1alpha2": - trafficSplits.ts2 = trafficSplitV1Alpha2(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight) + trafficSplits.ts2 = trafficSplitV1Alpha2(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight, additionalDestinations...) case "v1alpha3": - trafficSplits.ts3 = trafficSplitV1Alpha3(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight) + trafficSplits.ts3 = trafficSplitV1Alpha3(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight, additionalDestinations...) } return trafficSplits } @@ -260,59 +261,95 @@ func objectMeta(trafficSplitName string, ro *v1alpha1.Rollout, controllerKind sc } } -func trafficSplitV1Alpha1(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32) *smiv1alpha1.TrafficSplit { +func trafficSplitV1Alpha1(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) *smiv1alpha1.TrafficSplit { + backends := []smiv1alpha1.TrafficSplitBackend{{ + Service: ro.Spec.Strategy.Canary.CanaryService, + Weight: resource.NewQuantity(int64(desiredWeight), resource.DecimalExponent), + }} + stableWeight := int(100 - desiredWeight) + for _, dest := range additionalDestinations { + // Create backend entry + backends = append(backends, smiv1alpha1.TrafficSplitBackend{ + Service: dest.ServiceName, + Weight: resource.NewQuantity(int64(dest.Weight), resource.DecimalExponent), + }) + // Update stableWeight + stableWeight -= int(dest.Weight) + } + + // Add stable backend with fully updated stableWeight + backends = append(backends, smiv1alpha1.TrafficSplitBackend{ + Service: ro.Spec.Strategy.Canary.StableService, + Weight: resource.NewQuantity(int64(stableWeight), resource.DecimalExponent), + }) + return &smiv1alpha1.TrafficSplit{ ObjectMeta: objectMeta, Spec: smiv1alpha1.TrafficSplitSpec{ - Service: rootSvc, - Backends: []smiv1alpha1.TrafficSplitBackend{ - { - Service: ro.Spec.Strategy.Canary.CanaryService, - Weight: resource.NewQuantity(int64(desiredWeight), resource.DecimalExponent), - }, - { - Service: ro.Spec.Strategy.Canary.StableService, - Weight: resource.NewQuantity(int64(100-desiredWeight), resource.DecimalExponent), - }, - }, + Service: rootSvc, + Backends: backends, }, } } -func trafficSplitV1Alpha2(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32) *smiv1alpha2.TrafficSplit { +func trafficSplitV1Alpha2(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) *smiv1alpha2.TrafficSplit { + backends := []smiv1alpha2.TrafficSplitBackend{{ + Service: ro.Spec.Strategy.Canary.CanaryService, + Weight: int(desiredWeight), + }} + stableWeight := int(100 - desiredWeight) + for _, dest := range additionalDestinations { + // Create backend entry + backends = append(backends, smiv1alpha2.TrafficSplitBackend{ + Service: dest.ServiceName, + Weight: int(dest.Weight), + }) + // Update stableWeight + stableWeight -= int(dest.Weight) + } + + // Add stable backend with fully updated stableWeight + backends = append(backends, smiv1alpha2.TrafficSplitBackend{ + Service: ro.Spec.Strategy.Canary.StableService, + Weight: stableWeight, + }) + return &smiv1alpha2.TrafficSplit{ ObjectMeta: objectMeta, Spec: smiv1alpha2.TrafficSplitSpec{ - Service: rootSvc, - Backends: []smiv1alpha2.TrafficSplitBackend{ - { - Service: ro.Spec.Strategy.Canary.CanaryService, - Weight: int(desiredWeight), - }, - { - Service: ro.Spec.Strategy.Canary.StableService, - Weight: int(100 - desiredWeight), - }, - }, + Service: rootSvc, + Backends: backends, }, } } -func trafficSplitV1Alpha3(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32) *smiv1alpha3.TrafficSplit { +func trafficSplitV1Alpha3(ro *v1alpha1.Rollout, objectMeta metav1.ObjectMeta, rootSvc string, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) *smiv1alpha3.TrafficSplit { + backends := []smiv1alpha3.TrafficSplitBackend{{ + Service: ro.Spec.Strategy.Canary.CanaryService, + Weight: int(desiredWeight), + }} + stableWeight := int(100 - desiredWeight) + for _, dest := range additionalDestinations { + // Create backend entry + backends = append(backends, smiv1alpha3.TrafficSplitBackend{ + Service: dest.ServiceName, + Weight: int(dest.Weight), + }) + // Update stableWeight + stableWeight -= int(dest.Weight) + } + + // Add stable backend with fully updated stableWeight + backends = append(backends, smiv1alpha3.TrafficSplitBackend{ + Service: ro.Spec.Strategy.Canary.StableService, + Weight: stableWeight, + }) + return &smiv1alpha3.TrafficSplit{ ObjectMeta: objectMeta, Spec: smiv1alpha3.TrafficSplitSpec{ - Service: rootSvc, - Backends: []smiv1alpha3.TrafficSplitBackend{ - { - Service: ro.Spec.Strategy.Canary.CanaryService, - Weight: int(desiredWeight), - }, - { - Service: ro.Spec.Strategy.Canary.StableService, - Weight: int(100 - desiredWeight), - }, - }, + Service: rootSvc, + Backends: backends, }, } } diff --git a/rollout/trafficrouting/smi/smi_test.go b/rollout/trafficrouting/smi/smi_test.go index 705b01ea47..ba40903dcb 100644 --- a/rollout/trafficrouting/smi/smi_test.go +++ b/rollout/trafficrouting/smi/smi_test.go @@ -21,6 +21,7 @@ import ( k8stesting "k8s.io/client-go/testing" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/argoproj/argo-rollouts/utils/record" ) @@ -277,7 +278,6 @@ func TestReconcilePatchExistingTrafficSplit(t *testing.T) { } func TestReconcilePatchExistingTrafficSplitNoChange(t *testing.T) { - t.Run("v1alpha1", func(t *testing.T) { ro := fakeRollout("stable-service", "canary-service", "root-service", "traffic-split-v1alpha1") objMeta := objectMeta("traffic-split-v1alpha1", ro, schema.GroupVersionKind{}) @@ -432,3 +432,148 @@ func TestReconcileRolloutDoesNotOwnTrafficSplitError(t *testing.T) { assert.EqualError(t, err, "Rollout does not own TrafficSplit `traffic-split-name`") }) } + +func TestCreateTrafficSplitForMultipleBackends(t *testing.T) { + ro := fakeRollout("stable-service", "canary-service", "root-service", "traffic-split-name") + weightDestinations := []trafficrouting.WeightDestination{ + { + ServiceName: "ex-svc-1", + PodTemplateHash: "", + Weight: 5, + }, + { + ServiceName: "ex-svc-2", + PodTemplateHash: "", + Weight: 5, + }, + } + + t.Run("v1alpha1", func(t *testing.T) { + client := fake.NewSimpleClientset() + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{}, + }) + assert.Nil(t, err) + + err = r.SetWeight(10, weightDestinations...) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 2) + assert.Equal(t, "get", actions[0].GetVerb()) + assert.Equal(t, "create", actions[1].GetVerb()) + + // Get newly created TrafficSplit + obj := actions[1].(core.CreateAction).GetObject() + ts1 := &smiv1alpha1.TrafficSplit{} + converter := runtime.NewTestUnstructuredConverter(equality.Semantic) + objMap, _ := converter.ToUnstructured(obj) + runtime.NewTestUnstructuredConverter(equality.Semantic).FromUnstructured(objMap, ts1) + + // check canary backend + assert.Equal(t, "canary-service", ts1.Spec.Backends[0].Service) + assert.Equal(t, int64(10), ts1.Spec.Backends[0].Weight.Value()) + + // check experiment service backends + assert.Equal(t, weightDestinations[0].ServiceName, ts1.Spec.Backends[1].Service) + assert.Equal(t, int64(weightDestinations[0].Weight), ts1.Spec.Backends[1].Weight.Value()) + + assert.Equal(t, weightDestinations[1].ServiceName, ts1.Spec.Backends[2].Service) + assert.Equal(t, int64(weightDestinations[1].Weight), ts1.Spec.Backends[2].Weight.Value()) + + // check stable backend + assert.Equal(t, "stable-service", ts1.Spec.Backends[3].Service) + assert.Equal(t, int64(80), ts1.Spec.Backends[3].Weight.Value()) + }) + + t.Run("v1alpha2", func(t *testing.T) { + SetSMIAPIVersion("v1alpha2") + defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + + client := fake.NewSimpleClientset() + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{}, + }) + assert.Nil(t, err) + + err = r.SetWeight(10, weightDestinations...) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 2) + assert.Equal(t, "get", actions[0].GetVerb()) + assert.Equal(t, "create", actions[1].GetVerb()) + + // Get newly created TrafficSplit + obj := actions[1].(core.CreateAction).GetObject() + ts2 := &smiv1alpha2.TrafficSplit{} + converter := runtime.NewTestUnstructuredConverter(equality.Semantic) + objMap, _ := converter.ToUnstructured(obj) + runtime.NewTestUnstructuredConverter(equality.Semantic).FromUnstructured(objMap, ts2) + + // check canary backend + assert.Equal(t, "canary-service", ts2.Spec.Backends[0].Service) + assert.Equal(t, 10, ts2.Spec.Backends[0].Weight) + + // check experiment service backends + assert.Equal(t, weightDestinations[0].ServiceName, ts2.Spec.Backends[1].Service) + assert.Equal(t, int(weightDestinations[0].Weight), ts2.Spec.Backends[1].Weight) + + assert.Equal(t, weightDestinations[1].ServiceName, ts2.Spec.Backends[2].Service) + assert.Equal(t, int(weightDestinations[1].Weight), ts2.Spec.Backends[2].Weight) + + // check stable backend + assert.Equal(t, "stable-service", ts2.Spec.Backends[3].Service) + assert.Equal(t, 80, ts2.Spec.Backends[3].Weight) + }) + + t.Run("v1alpha3", func(t *testing.T) { + SetSMIAPIVersion("v1alpha3") + defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + + client := fake.NewSimpleClientset() + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{}, + }) + assert.Nil(t, err) + + err = r.SetWeight(10, weightDestinations...) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, actions, 2) + assert.Equal(t, "get", actions[0].GetVerb()) + assert.Equal(t, "create", actions[1].GetVerb()) + + // Get newly created TrafficSplit + obj := actions[1].(core.CreateAction).GetObject() + ts3 := &smiv1alpha3.TrafficSplit{} + converter := runtime.NewTestUnstructuredConverter(equality.Semantic) + objMap, _ := converter.ToUnstructured(obj) + runtime.NewTestUnstructuredConverter(equality.Semantic).FromUnstructured(objMap, ts3) + + // check canary backend + assert.Equal(t, "canary-service", ts3.Spec.Backends[0].Service) + assert.Equal(t, 10, ts3.Spec.Backends[0].Weight) + + // check experiment service backends + assert.Equal(t, weightDestinations[0].ServiceName, ts3.Spec.Backends[1].Service) + assert.Equal(t, int(weightDestinations[0].Weight), ts3.Spec.Backends[1].Weight) + + assert.Equal(t, weightDestinations[1].ServiceName, ts3.Spec.Backends[2].Service) + assert.Equal(t, int(weightDestinations[1].Weight), ts3.Spec.Backends[2].Weight) + + // check stable backend + assert.Equal(t, "stable-service", ts3.Spec.Backends[3].Service) + assert.Equal(t, 80, ts3.Spec.Backends[3].Weight) + }) +} diff --git a/rollout/trafficrouting/trafficroutingutil.go b/rollout/trafficrouting/trafficroutingutil.go new file mode 100644 index 0000000000..e9b609314e --- /dev/null +++ b/rollout/trafficrouting/trafficroutingutil.go @@ -0,0 +1,20 @@ +package trafficrouting + +// TrafficRoutingReconciler common function across all TrafficRouting implementation +type TrafficRoutingReconciler interface { + // UpdateHash informs a traffic routing reconciler about new canary/stable pod hashes + UpdateHash(canaryHash, stableHash string) error + // SetWeight sets the canary weight to the desired weight + SetWeight(desiredWeight int32, additionalDestinations ...WeightDestination) error + // VerifyWeight returns true if the canary is at the desired weight + VerifyWeight(desiredWeight int32) (bool, error) + // Type returns the type of the traffic routing reconciler + Type() string +} + +// WeightDestination common struct +type WeightDestination struct { + ServiceName string + PodTemplateHash string + Weight int32 +} diff --git a/rollout/trafficrouting_test.go b/rollout/trafficrouting_test.go index 68bb189e07..57fb408f27 100644 --- a/rollout/trafficrouting_test.go +++ b/rollout/trafficrouting_test.go @@ -16,6 +16,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/rollout/mocks" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/alb" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/istio" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/nginx" @@ -161,7 +162,7 @@ func TestRolloutUseDesiredWeight(t *testing.T) { f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("SetWeight", mock.Anything).Return(func(desiredWeight int32) error { + f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(func(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { // make sure SetWeight was called with correct value assert.Equal(t, int32(10), desiredWeight) return nil @@ -170,6 +171,98 @@ func TestRolloutUseDesiredWeight(t *testing.T) { f.run(getKey(r2, t)) } +func TestRolloutWithExperimentStep(t *testing.T) { + f := newFixture(t) + defer f.Close() + + steps := []v1alpha1.CanaryStep{ + { + SetWeight: pointer.Int32Ptr(10), + }, + { + Experiment: &v1alpha1.RolloutExperimentStep{ + Templates: []v1alpha1.RolloutExperimentTemplate{{ + Name: "experiment-template", + SpecRef: "canary", + Replicas: pointer.Int32Ptr(1), + Weight: pointer.Int32Ptr(5), + }}, + }, + }, + } + r1 := newCanaryRollout("foo", 10, nil, steps, pointer.Int32Ptr(1), intstr.FromInt(1), intstr.FromInt(0)) + r2 := bumpVersion(r1) + r2.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{} + r2.Spec.Strategy.Canary.CanaryService = "canary" + r2.Spec.Strategy.Canary.StableService = "stable" + + rs1 := newReplicaSetWithStatus(r1, 10, 10) + rs2 := newReplicaSetWithStatus(r2, 1, 1) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + canarySelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} + stableSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} + canarySvc := newService("canary", 80, canarySelector, r2) + stableSvc := newService("stable", 80, stableSelector, r2) + ex, _ := GetExperimentFromTemplate(r1, rs1, rs2) + ex.Status.TemplateStatuses = []v1alpha1.TemplateStatus{{ + Name: "experiment-template", + ServiceName: "experiment-service", + PodTemplateHash: rs2PodHash, + }} + r2.Status.Canary.CurrentExperiment = ex.Name + + f.kubeobjects = append(f.kubeobjects, rs1, rs2, canarySvc, stableSvc) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + + r2 = updateCanaryRolloutStatus(r2, rs1PodHash, 10, 0, 10, false) + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2, ex) + + f.expectPatchRolloutAction(r2) + + t.Run("Experiment Running - WeightDestination created", func(t *testing.T) { + ex.Status.Phase = v1alpha1.AnalysisPhaseRunning + f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() + f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(func(desiredWeight int32, weightDestinations ...trafficrouting.WeightDestination) error { + // make sure SetWeight was called with correct value + assert.Equal(t, int32(10), desiredWeight) + assert.Equal(t, int32(5), weightDestinations[0].Weight) + assert.Equal(t, ex.Status.TemplateStatuses[0].ServiceName, weightDestinations[0].ServiceName) + assert.Equal(t, ex.Status.TemplateStatuses[0].PodTemplateHash, weightDestinations[0].PodTemplateHash) + return nil + }) + f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(func(desiredWeight int32, weightDestinations ...trafficrouting.WeightDestination) error { + assert.Equal(t, int32(10), desiredWeight) + assert.Equal(t, int32(5), weightDestinations[0].Weight) + assert.Equal(t, ex.Status.TemplateStatuses[0].ServiceName, weightDestinations[0].ServiceName) + assert.Equal(t, ex.Status.TemplateStatuses[0].PodTemplateHash, weightDestinations[0].PodTemplateHash) + return nil + }) + f.run(getKey(r2, t)) + }) + + t.Run("Experiment Pending - no WeightDestination created", func(t *testing.T) { + ex.Status.Phase = v1alpha1.AnalysisPhasePending + f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() + f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything).Return(nil) + f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(func(desiredWeight int32, weightDestinations ...trafficrouting.WeightDestination) error { + // make sure SetWeight was called with correct value + assert.Equal(t, int32(10), desiredWeight) + assert.Len(t, weightDestinations, 0) + return nil + }) + f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(func(desiredWeight int32, weightDestinations ...trafficrouting.WeightDestination) error { + assert.Equal(t, int32(10), desiredWeight) + assert.Len(t, weightDestinations, 0) + return nil + }) + f.run(getKey(r2, t)) + }) +} + func TestRolloutUsePreviousSetWeight(t *testing.T) { f := newFixture(t) defer f.Close() @@ -210,7 +303,7 @@ func TestRolloutUsePreviousSetWeight(t *testing.T) { f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("SetWeight", mock.Anything).Return(func(desiredWeight int32) error { + f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(func(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { // make sure SetWeight was called with correct value assert.Equal(t, int32(10), desiredWeight) return nil @@ -251,7 +344,7 @@ func TestRolloutSetWeightToZeroWhenFullyRolledOut(t *testing.T) { f.expectPatchRolloutAction(r1) f.fakeTrafficRouting = newUnmockedFakeTrafficRoutingReconciler() f.fakeTrafficRouting.On("UpdateHash", mock.Anything, mock.Anything).Return(nil) - f.fakeTrafficRouting.On("SetWeight", mock.Anything).Return(func(desiredWeight int32) error { + f.fakeTrafficRouting.On("SetWeight", mock.Anything, mock.Anything).Return(func(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) error { // make sure SetWeight was called with correct value assert.Equal(t, int32(0), desiredWeight) return nil diff --git a/test/e2e/crds/split.yaml b/test/e2e/crds/split.yaml new file mode 100644 index 0000000000..04aa266b71 --- /dev/null +++ b/test/e2e/crds/split.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: trafficsplits.split.smi-spec.io +spec: + group: split.smi-spec.io + version: v1alpha1 + scope: Namespaced + names: + kind: TrafficSplit + listKind: TrafficSplitList + shortNames: + - ts + plural: trafficsplits + singular: trafficsplit + versions: + - name: v1alpha1 + served: true + storage: true + additionalPrinterColumns: + - name: Service + type: string + description: The apex service of this split. + JSONPath: .spec.service diff --git a/test/e2e/smi/rollout-smi-experiment.yaml b/test/e2e/smi/rollout-smi-experiment.yaml new file mode 100644 index 0000000000..88e5a529e4 --- /dev/null +++ b/test/e2e/smi/rollout-smi-experiment.yaml @@ -0,0 +1,87 @@ +apiVersion: v1 +kind: Service +metadata: + name: rollout-smi-experiment-canary +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: rollout-smi-experiment + # This selector will be updated with the pod-template-hash of the canary ReplicaSet. e.g.: + # rollouts-pod-template-hash: 7bf84f9696 +--- +apiVersion: v1 +kind: Service +metadata: + name: rollout-smi-experiment-stable +spec: + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: rollout-smi-experiment + # This selector will be updated with the pod-template-hash of the stable ReplicaSet. e.g.: + # rollouts-pod-template-hash: 789746c88d +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: rollout-smi-experiment-stable + annotations: + kubernetes.io/ingress.class: nginx +spec: + rules: + - host: rollout-smi-experiment.local + http: + paths: + - path: / + backend: + # Reference to a Service name, also specified in the Rollout spec.strategy.canary.stableService field + serviceName: rollout-smi-experiment-stable + servicePort: 80 +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-smi-experiment +spec: + replicas: 1 + strategy: + canary: + canaryService: rollout-smi-experiment-canary + stableService: rollout-smi-experiment-stable + trafficRouting: + smi: + trafficSplitName: rollout-smi-experiment-trafficsplit + steps: + - setWeight: 5 + - experiment: + templates: + - name: experiment-smi + specRef: canary + weight: 5 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: rollout-smi-experiment + template: + metadata: + labels: + app: rollout-smi-experiment + spec: + containers: + - name: rollout-smi-experiment + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m diff --git a/test/e2e/smi_test.go b/test/e2e/smi_test.go new file mode 100644 index 0000000000..ff92cbcc8e --- /dev/null +++ b/test/e2e/smi_test.go @@ -0,0 +1,104 @@ +// +build e2e + +package e2e + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/tj/assert" + + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/test/fixtures" +) + +type SMISuite struct { + fixtures.E2ESuite +} + +func TestSMISuite(t *testing.T) { + suite.Run(t, new(SMISuite)) +} + +func (s *SMISuite) SetupSuite() { + s.E2ESuite.SetupSuite() + if !s.SMIEnabled { + s.T().SkipNow() + } +} + +func (s *SMISuite) TestSMIExperimentStep() { + s.Given(). + RolloutObjects("@smi/rollout-smi-experiment.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + ts := t.GetTrafficSplit() + + assert.Len(s.T(), ts.Spec.Backends, 2) + + assert.Equal(s.T(), "rollout-smi-experiment-canary", ts.Spec.Backends[0].Service) + assert.Equal(s.T(), int64(0), ts.Spec.Backends[0].Weight.Value()) + + assert.Equal(s.T(), "rollout-smi-experiment-stable", ts.Spec.Backends[1].Service) + assert.Equal(s.T(), int64(100), ts.Spec.Backends[1].Weight.Value()) + + desired, stable := t.GetServices() + rs1 := t.GetReplicaSetByRevision("1") + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + ExpectExperimentCount(0). + When(). + UpdateSpec(). + WaitForRolloutCanaryStepIndex(1). + Sleep(5*time.Second). + Then(). + Assert(func(t *fixtures.Then) { + ts := t.GetTrafficSplit() + + assert.Len(s.T(), ts.Spec.Backends, 3) + + assert.Equal(s.T(), "rollout-smi-experiment-canary", ts.Spec.Backends[0].Service) + assert.Equal(s.T(), int64(5), ts.Spec.Backends[0].Weight.Value()) + + ex := t.GetRolloutExperiments().Items[0] + exServiceName := ex.Status.TemplateStatuses[0].ServiceName + assert.Equal(s.T(), int64(5), ts.Spec.Backends[1].Weight.Value()) + assert.Equal(s.T(), exServiceName, ts.Spec.Backends[1].Service) + + assert.Equal(s.T(), "rollout-smi-experiment-stable", ts.Spec.Backends[2].Service) + assert.Equal(s.T(), int64(90), ts.Spec.Backends[2].Weight.Value()) + + desired, stable := t.GetServices() + rs1 := t.GetReplicaSetByRevision("1") + rs2 := t.GetReplicaSetByRevision("2") + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs1.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1*time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(func(t *fixtures.Then) { + ts := t.GetTrafficSplit() + + assert.Len(s.T(), ts.Spec.Backends, 2) + + assert.Equal(s.T(), "rollout-smi-experiment-canary", ts.Spec.Backends[0].Service) + assert.Equal(s.T(), int64(0), ts.Spec.Backends[0].Weight.Value()) + + assert.Equal(s.T(), "rollout-smi-experiment-stable", ts.Spec.Backends[1].Service) + assert.Equal(s.T(), int64(100), ts.Spec.Backends[1].Weight.Value()) + + desired, stable := t.GetServices() + rs2 := t.GetReplicaSetByRevision("2") + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], desired.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + assert.Equal(s.T(), rs2.Spec.Template.Labels[v1alpha1.DefaultRolloutUniqueLabelKey], stable.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey]) + }). + ExpectRevisionPodCount("1", 1) // don't scale down old replicaset since it will be within scaleDownDelay +} diff --git a/test/fixtures/common.go b/test/fixtures/common.go index 0793934292..77a48b0d95 100644 --- a/test/fixtures/common.go +++ b/test/fixtures/common.go @@ -13,6 +13,10 @@ import ( "text/tabwriter" "time" + smiv1alpha1 "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/split/v1alpha1" + + smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" + "github.com/ghodss/yaml" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" @@ -51,6 +55,7 @@ type Common struct { kubeClient kubernetes.Interface dynamicClient dynamic.Interface rolloutClient clientset.Interface + smiClient smiclientset.Interface rollout *unstructured.Unstructured objects []*unstructured.Unstructured @@ -478,6 +483,14 @@ func (c *Common) GetServices() (*corev1.Service, *corev1.Service) { return desiredSvc, stableSvc } +func (c *Common) GetTrafficSplit() *smiv1alpha1.TrafficSplit { + ro := c.Rollout() + name := ro.Spec.Strategy.Canary.TrafficRouting.SMI.TrafficSplitName + ts, err := c.smiClient.SplitV1alpha1().TrafficSplits(c.namespace).Get(c.Context, name, metav1.GetOptions{}) + c.CheckError(err) + return ts +} + func (c *Common) GetVirtualService() *istio.VirtualService { ro := c.Rollout() name := ro.Spec.Strategy.Canary.TrafficRouting.Istio.VirtualService.Name diff --git a/test/fixtures/e2e_suite.go b/test/fixtures/e2e_suite.go index dc75a6d960..71589276cf 100644 --- a/test/fixtures/e2e_suite.go +++ b/test/fixtures/e2e_suite.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -27,6 +28,7 @@ import ( clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" istioutil "github.com/argoproj/argo-rollouts/utils/istio" logutil "github.com/argoproj/argo-rollouts/utils/log" + smiutil "github.com/argoproj/argo-rollouts/utils/smi" ) const ( @@ -115,6 +117,7 @@ type E2ESuite struct { Common IstioEnabled bool + SMIEnabled bool } func (s *E2ESuite) SetupSuite() { @@ -142,11 +145,17 @@ func (s *E2ESuite) SetupSuite() { s.CheckError(err) s.rolloutClient, err = clientset.NewForConfig(restConfig) s.CheckError(err) + s.smiClient, err = smiclientset.NewForConfig(restConfig) + s.CheckError(err) s.log = log.NewEntry(log.StandardLogger()) if istioutil.DoesIstioExist(s.dynamicClient, s.namespace) { s.IstioEnabled = true } + + if smiutil.DoesSMIExist(s.smiClient, s.namespace) { + s.SMIEnabled = true + } } func (s *E2ESuite) TearDownSuite() { diff --git a/utils/smi/smi.go b/utils/smi/smi.go new file mode 100644 index 0000000000..1074aca9a1 --- /dev/null +++ b/utils/smi/smi.go @@ -0,0 +1,16 @@ +package smi + +import ( + "context" + + smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func DoesSMIExist(smiClient smiclientset.Interface, namespace string) bool { + _, err := smiClient.SplitV1alpha1().TrafficSplits(namespace).List(context.TODO(), metav1.ListOptions{Limit: 1}) + if err != nil { + return false + } + return true +} From f9d29403fe840eeb09adb15bb1c9074dd4f070b7 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Mon, 23 Aug 2021 16:00:28 -0700 Subject: [PATCH 24/34] chore: skip e2e only if workflow started manually with debug enabled (#1441) Signed-off-by: Alexander Matyushentsev --- .github/workflows/e2e.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 044630ac47..d59956ceb7 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -48,7 +48,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled}} - name: Run e2e tests run: make test-e2e - if: ${{ github.event_name == 'workflow_dispatch' && !github.event.inputs.debug_enabled}} + if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled) }} - name: Upload e2e-controller logs uses: actions/upload-artifact@v2 with: From 601174abc17c1fcfcf133c8fd74e751a4350faf8 Mon Sep 17 00:00:00 2001 From: Reshma Chowdary Morampudi <72629150+rmorampudi@users.noreply.github.com> Date: Tue, 24 Aug 2021 15:19:17 -0700 Subject: [PATCH 25/34] fix: Remove the type from the resource name (#1265) Signed-off-by: reshma-morampudi Signed-off-by: Jesse Suen Co-authored-by: Jesse Suen --- .../argo-rollouts-clusterrolebinding.yaml | 6 +- manifests/install.yaml | 178 +++++++++--------- manifests/namespace-install.yaml | 10 +- .../argo-rollouts-rolebinding.yaml | 6 +- .../clusterrole-to-role.yaml | 6 - .../namespace-install/kustomization.yaml | 2 +- manifests/role/argo-rollouts-clusterrole.yaml | 4 +- 7 files changed, 103 insertions(+), 109 deletions(-) diff --git a/manifests/cluster-install/argo-rollouts-clusterrolebinding.yaml b/manifests/cluster-install/argo-rollouts-clusterrolebinding.yaml index ca25de7f58..798ee14ca5 100644 --- a/manifests/cluster-install/argo-rollouts-clusterrolebinding.yaml +++ b/manifests/cluster-install/argo-rollouts-clusterrolebinding.yaml @@ -1,15 +1,15 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: argo-rollouts-clusterrolebinding + name: argo-rollouts labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-clusterrolebinding + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: argo-rollouts-clusterrole + name: argo-rollouts subjects: - kind: ServiceAccount name: argo-rollouts diff --git a/manifests/install.yaml b/manifests/install.yaml index 7bd272333c..9fe2b1648b 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -12576,96 +12576,12 @@ metadata: --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: aggregate-cluster-role - app.kubernetes.io/name: argo-rollouts-aggregate-to-admin - app.kubernetes.io/part-of: argo-rollouts - rbac.authorization.k8s.io/aggregate-to-admin: "true" - name: argo-rollouts-aggregate-to-admin -rules: -- apiGroups: - - argoproj.io - resources: - - rollouts - - rollouts/scale - - rollouts/status - - experiments - - analysistemplates - - clusteranalysistemplates - - analysisruns - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: aggregate-cluster-role - app.kubernetes.io/name: argo-rollouts-aggregate-to-edit - app.kubernetes.io/part-of: argo-rollouts - rbac.authorization.k8s.io/aggregate-to-edit: "true" - name: argo-rollouts-aggregate-to-edit -rules: -- apiGroups: - - argoproj.io - resources: - - rollouts - - rollouts/scale - - rollouts/status - - experiments - - analysistemplates - - clusteranalysistemplates - - analysisruns - verbs: - - create - - delete - - deletecollection - - get - - list - - patch - - update - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/component: aggregate-cluster-role - app.kubernetes.io/name: argo-rollouts-aggregate-to-view - app.kubernetes.io/part-of: argo-rollouts - rbac.authorization.k8s.io/aggregate-to-view: "true" - name: argo-rollouts-aggregate-to-view -rules: -- apiGroups: - - argoproj.io - resources: - - rollouts - - rollouts/scale - - experiments - - analysistemplates - - clusteranalysistemplates - - analysisruns - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole metadata: labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-clusterrole + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts - name: argo-rollouts-clusterrole + name: argo-rollouts rules: - apiGroups: - argoproj.io @@ -12832,17 +12748,101 @@ rules: - delete --- apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: aggregate-cluster-role + app.kubernetes.io/name: argo-rollouts-aggregate-to-admin + app.kubernetes.io/part-of: argo-rollouts + rbac.authorization.k8s.io/aggregate-to-admin: "true" + name: argo-rollouts-aggregate-to-admin +rules: +- apiGroups: + - argoproj.io + resources: + - rollouts + - rollouts/scale + - rollouts/status + - experiments + - analysistemplates + - clusteranalysistemplates + - analysisruns + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: aggregate-cluster-role + app.kubernetes.io/name: argo-rollouts-aggregate-to-edit + app.kubernetes.io/part-of: argo-rollouts + rbac.authorization.k8s.io/aggregate-to-edit: "true" + name: argo-rollouts-aggregate-to-edit +rules: +- apiGroups: + - argoproj.io + resources: + - rollouts + - rollouts/scale + - rollouts/status + - experiments + - analysistemplates + - clusteranalysistemplates + - analysisruns + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/component: aggregate-cluster-role + app.kubernetes.io/name: argo-rollouts-aggregate-to-view + app.kubernetes.io/part-of: argo-rollouts + rbac.authorization.k8s.io/aggregate-to-view: "true" + name: argo-rollouts-aggregate-to-view +rules: +- apiGroups: + - argoproj.io + resources: + - rollouts + - rollouts/scale + - experiments + - analysistemplates + - clusteranalysistemplates + - analysisruns + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-clusterrolebinding + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts - name: argo-rollouts-clusterrolebinding + name: argo-rollouts roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: argo-rollouts-clusterrole + name: argo-rollouts subjects: - kind: ServiceAccount name: argo-rollouts diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index eb0e529e79..c971be8405 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -12579,9 +12579,9 @@ kind: Role metadata: labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-role + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts - name: argo-rollouts-role + name: argo-rollouts rules: - apiGroups: - argoproj.io @@ -12836,13 +12836,13 @@ kind: RoleBinding metadata: labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-role-binding + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts - name: argo-rollouts-role-binding + name: argo-rollouts roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: argo-rollouts-role + name: argo-rollouts subjects: - kind: ServiceAccount name: argo-rollouts diff --git a/manifests/namespace-install/argo-rollouts-rolebinding.yaml b/manifests/namespace-install/argo-rollouts-rolebinding.yaml index ace4fe08a1..5b67bc4cb5 100644 --- a/manifests/namespace-install/argo-rollouts-rolebinding.yaml +++ b/manifests/namespace-install/argo-rollouts-rolebinding.yaml @@ -1,15 +1,15 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: argo-rollouts-role-binding + name: argo-rollouts labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-role-binding + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: argo-rollouts-role + name: argo-rollouts subjects: - kind: ServiceAccount name: argo-rollouts diff --git a/manifests/namespace-install/clusterrole-to-role.yaml b/manifests/namespace-install/clusterrole-to-role.yaml index d012454170..657cff240c 100644 --- a/manifests/namespace-install/clusterrole-to-role.yaml +++ b/manifests/namespace-install/clusterrole-to-role.yaml @@ -1,9 +1,3 @@ -- op: replace - path: /metadata/name - value: argo-rollouts-role - op: replace path: /kind value: Role -- op: replace - path: /metadata/labels/app.kubernetes.io~1name - value: argo-rollouts-role diff --git a/manifests/namespace-install/kustomization.yaml b/manifests/namespace-install/kustomization.yaml index 853bd41f8b..c3d732d2db 100644 --- a/manifests/namespace-install/kustomization.yaml +++ b/manifests/namespace-install/kustomization.yaml @@ -17,5 +17,5 @@ patchesJson6902: target: group: rbac.authorization.k8s.io kind: ClusterRole - name: argo-rollouts-clusterrole + name: argo-rollouts version: v1 diff --git a/manifests/role/argo-rollouts-clusterrole.yaml b/manifests/role/argo-rollouts-clusterrole.yaml index ac6076df8e..6162c4e7b6 100644 --- a/manifests/role/argo-rollouts-clusterrole.yaml +++ b/manifests/role/argo-rollouts-clusterrole.yaml @@ -1,10 +1,10 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: argo-rollouts-clusterrole + name: argo-rollouts labels: app.kubernetes.io/component: rollouts-controller - app.kubernetes.io/name: argo-rollouts-clusterrole + app.kubernetes.io/name: argo-rollouts app.kubernetes.io/part-of: argo-rollouts rules: - apiGroups: From 74c7681c65f61373ee3bb9247f758fb465f20bf4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:05:00 -0700 Subject: [PATCH 26/34] chore(deps): bump github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 (#1435) Bumps [github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2](https://github.com/aws/aws-sdk-go-v2) from 1.0.0 to 1.6.1. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/v1.0.0...config/v1.6.1) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 3 ++- go.sum | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 465d5f78f5..9e588aa483 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,8 @@ require ( github.com/argoproj/notifications-engine v0.2.1-0.20210525191332-e8e293898477 github.com/argoproj/pkg v0.9.0 github.com/aws/aws-sdk-go-v2/config v1.0.0 - github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.0.0 + github.com/aws/aws-sdk-go-v2/internal/ini v1.2.1 // indirect + github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.6.1 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/evanphx/json-patch/v5 v5.2.0 github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 diff --git a/go.sum b/go.sum index ed672b8a52..3decf77c83 100644 --- a/go.sum +++ b/go.sum @@ -176,22 +176,26 @@ github.com/aws/aws-sdk-go v1.31.13/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZve 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-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/aws/aws-sdk-go-v2 v1.0.0 h1:ncEVPoHArsG+HjoDe/3ex/TG1CbLwMQ4eaWj0UGdyTo= github.com/aws/aws-sdk-go-v2 v1.0.0/go.mod h1:smfAbmpW+tcRVuNUjo3MOArSZmW72t62rkCzc2i0TWM= +github.com/aws/aws-sdk-go-v2 v1.8.1 h1:GcFgQl7MsBygmeeqXyV1ivrTEmsVz/rdFJaTcltG9ag= +github.com/aws/aws-sdk-go-v2 v1.8.1/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= github.com/aws/aws-sdk-go-v2/config v1.0.0 h1:x6vSFAwqAvhYPeSu60f0ZUlGHo3PKKmwDOTL8aMXtv4= github.com/aws/aws-sdk-go-v2/config v1.0.0/go.mod h1:WysE/OpUgE37tjtmtJd8GXgT8s1euilE5XtUkRNUQ1w= github.com/aws/aws-sdk-go-v2/credentials v1.0.0 h1:0M7netgZ8gCV4v7z1km+Fbl7j6KQYyZL7SS0/l5Jn/4= github.com/aws/aws-sdk-go-v2/credentials v1.0.0/go.mod h1:/SvsiqBf509hG4Bddigr3NB12MIpfHhZapyBurJe8aY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.0 h1:lO7fH5n7Q1dKcDBpuTmwJylD1bOQiRig8LI6TD9yVQk= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.0/go.mod h1:wpMHDCXvOXZxGCRSidyepa8uJHY4vaBGfY2/+oKU/Bc= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.0.0 h1:OJnzXg++TleNvDO+/Ysx+8XPiz2VxoPJ1UdiyL9fVHY= -github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.0.0/go.mod h1:n5YmmB7VY/iK0TtXWSUkuO8dx11DXoMeNJ5HrCYJSQs= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.1 h1:IkqRRUZTKaS16P2vpX+FNc2jq3JWa3c478gykQp4ow4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.1/go.mod h1:Pv3WenDjI0v2Jl7UaMFIIbPOBbhn33RmmAmGgkXDoqY= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.6.1 h1:mGc8UvJS4XJv8Tp7Doxlx2p3vfwPx46K9zg+9s9szPE= +github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.6.1/go.mod h1:lGKz4aJbqGX+pgyXG47ZBAJPjwrlA5+TJsAuJ2+aE2g= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.0 h1:IAutMPSrynpvKOpHG6HyWHmh1xmxWAmYOK84NrQVqVQ= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.0/go.mod h1:3jExOmpbjgPnz2FJaMOfbSk1heTkZ66aD3yNtVhnjvI= github.com/aws/aws-sdk-go-v2/service/sts v1.0.0 h1:6XCgxNfE4L/Fnq+InhVNd16DKc6Ue1f3dJl3IwwJRUQ= github.com/aws/aws-sdk-go-v2/service/sts v1.0.0/go.mod h1:5f+cELGATgill5Pu3/vK3Ebuigstc+qYEHW5MvGWZO4= -github.com/aws/smithy-go v1.0.0 h1:hkhcRKG9rJ4Fn+RbfXY7Tz7b3ITLDyolBnLLBhwbg/c= github.com/aws/smithy-go v1.0.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= +github.com/aws/smithy-go v1.7.0 h1:+cLHMRrDZvQ4wk+KuQ9yH6eEg6KZEJ9RI2IkDqnygCg= +github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -526,8 +530,9 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/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-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= @@ -665,7 +670,9 @@ github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= From 61416857c27a5f3ff1631ac7505890555ce2ad11 Mon Sep 17 00:00:00 2001 From: Rohit Agrawal Date: Tue, 24 Aug 2021 19:07:04 -0400 Subject: [PATCH 27/34] docs: Add some more explanation around TLS routes in Istio-based Rollouts README (#1414) Signed-off-by: Rohit Agrawal --- docs/getting-started/istio/index.md | 33 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/getting-started/istio/index.md b/docs/getting-started/istio/index.md index 57c7b3b136..d805465144 100644 --- a/docs/getting-started/istio/index.md +++ b/docs/getting-started/istio/index.md @@ -47,15 +47,18 @@ spec: ... ``` -The VirtualService and route referenced in `trafficRouting.istio.virtualService` is required -to have either an HTTP or a TLS route spec that splits between the stable and the canary services, -referenced in the rollout. If the route is of type HTTPS/TLS, then we can match it based on the +The VirtualService and route referenced in `trafficRouting.istio.virtualService` are required +to have either HTTP or TLS, or both route specs that splits between the stable and the canary +services referenced in the rollout. If the route is HTTPS/TLS, we can match it based on the given port number and/or SNI hosts. Note that both of them are optional and only needed if you -want to match any rule in your VirtualService which contain these. +want to match any rule in your VirtualService which contains these. -In this guide, the two services are named: `rollouts-demo-stable` and `rollouts-demo-canary` -respectively. The weight values for these services used should be initially set to 100% stable, -and 0% on the canary. During an update, these values will be modified by the controller. +In this guide, the two services are: `rollouts-demo-stable` and `rollouts-demo-canary` respectively. +The weights for these two services should initially be set to 100% on the stable service and 0% on +the canary service. During an update, these values will get modified by the controller. + +Note that since we have both the HTTP and HTTPS routes in our rollout spec and they match the +VirtualService specs, weights will get modified for both these routes. ```yaml apiVersion: networking.istio.io/v1alpha3 @@ -68,25 +71,25 @@ spec: hosts: - rollouts-demo.local http: - - name: http-primary # Should match spec.strategy.canary.trafficRouting.istio.virtualService.routes + - name: http-primary # Should match rollout.spec.strategy.canary.trafficRouting.istio.virtualService.routes route: - destination: - host: rollouts-demo-stable # Should match spec.strategy.canary.stableService + host: rollouts-demo-stable # Should match rollout.spec.strategy.canary.stableService weight: 100 - destination: - host: rollouts-demo-canary # Should match spec.strategy.canary.canaryService + host: rollouts-demo-canary # Should match rollout.spec.strategy.canary.canaryService weight: 0 tls: - match: - - port: 443 # Should match the port number of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes - sniHosts: # Should match all the SNI hosts of the route defined in spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes + - port: 443 # Should match the port number of the route defined in rollout.spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes + sniHosts: # Should match all the SNI hosts of the route defined in rollout.spec.strategy.canary.trafficRouting.istio.virtualService.tlsRoutes - reviews.bookinfo.com route: - destination: - host: rollouts-demo-stable # Should match spec.strategy.canary.stableService + host: rollouts-demo-stable # Should match rollout.spec.strategy.canary.stableService weight: 100 - destination: - host: rollouts-demo-canary # Should match spec.strategy.canary.canaryService + host: rollouts-demo-canary # Should match rollout.spec.strategy.canary.canaryService weight: 0 ``` @@ -171,7 +174,7 @@ spec: weight: 5 tls: - match: - - port: 3000 + - port: 443 sniHosts: - reviews.bookinfo.com route: From 37d0c4b376cabebb015ef86fe60fb5accf82b714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Aug 2021 00:15:24 -0700 Subject: [PATCH 28/34] chore(deps): bump codecov/codecov-action from 1 to 2.0.3 (#1446) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 2.0.3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v1...v2.0.3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index b4e7c210f8..6ba4e33578 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -55,7 +55,7 @@ jobs: path: coverage.out - name: Upload code coverage information to codecov.io - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2.0.3 with: file: coverage.out From a601a0c18e657ae81a0269b229767dcbe9bbf56a Mon Sep 17 00:00:00 2001 From: Kareena Hirani Date: Wed, 25 Aug 2021 12:41:21 -0700 Subject: [PATCH 29/34] fix: Analysis argument validation (#1412) * fix: Analysis argument validation Signed-off-by: khhirani --- analysis/analysis.go | 81 +++++++++++++------ analysis/analysis_test.go | 64 +++++++-------- test/e2e/analysis_test.go | 60 +++++++++++++- .../analysistemplate-web-background.yaml | 5 +- .../rollout-bg-analysis-withArgs.yaml | 74 +++++++++++++++++ .../functional/rollout-secret-withArgs.yaml | 78 ++++++++++++++++++ test/e2e/functional/rollout-secret.yaml | 13 ++- 7 files changed, 315 insertions(+), 60 deletions(-) create mode 100644 test/e2e/functional/rollout-bg-analysis-withArgs.yaml create mode 100644 test/e2e/functional/rollout-secret-withArgs.yaml diff --git a/analysis/analysis.go b/analysis/analysis.go index ab2d6b9741..73aadb5eca 100644 --- a/analysis/analysis.go +++ b/analysis/analysis.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "k8s.io/utils/pointer" + log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -43,20 +45,31 @@ func (c *Controller) reconcileAnalysisRun(origRun *v1alpha1.AnalysisRun) *v1alph if run.Status.MetricResults == nil { run.Status.MetricResults = make([]v1alpha1.MetricResult, 0) - err := analysisutil.ValidateMetrics(run.Spec.Metrics) - if err != nil { - message := fmt.Sprintf("analysis spec invalid: %v", err) - log.Warn(message) - run.Status.Phase = v1alpha1.AnalysisPhaseError - run.Status.Message = message - c.recordAnalysisRunCompletionEvent(run) - return run - } } - tasks := generateMetricTasks(run) + resolvedMetrics, err := getResolvedMetricsWithoutSecrets(run.Spec.Metrics, run.Spec.Args) + if err != nil { + message := fmt.Sprintf("unable to resolve metric arguments: %v", err) + log.Warn(message) + run.Status.Phase = v1alpha1.AnalysisPhaseError + run.Status.Message = message + c.recordAnalysisRunCompletionEvent(run) + return run + } + + err = analysisutil.ValidateMetrics(resolvedMetrics) + if err != nil { + message := fmt.Sprintf("analysis spec invalid: %v", err) + log.Warn(message) + run.Status.Phase = v1alpha1.AnalysisPhaseError + run.Status.Message = message + c.recordAnalysisRunCompletionEvent(run) + return run + } + + tasks := generateMetricTasks(run, resolvedMetrics) log.Infof("taking %d measurements", len(tasks)) - err := c.runMeasurements(run, tasks) + err = c.runMeasurements(run, tasks) if err != nil { message := fmt.Sprintf("unable to resolve metric arguments: %v", err) log.Warn(message) @@ -66,7 +79,7 @@ func (c *Controller) reconcileAnalysisRun(origRun *v1alpha1.AnalysisRun) *v1alph return run } - newStatus, newMessage := c.assessRunStatus(run) + newStatus, newMessage := c.assessRunStatus(run, resolvedMetrics) if newStatus != run.Status.Phase { run.Status.Phase = newStatus run.Status.Message = newMessage @@ -81,7 +94,7 @@ func (c *Controller) reconcileAnalysisRun(origRun *v1alpha1.AnalysisRun) *v1alph log.Warnf("Failed to garbage collect measurements: %v", err) } - nextReconcileTime := calculateNextReconcileTime(run) + nextReconcileTime := calculateNextReconcileTime(run, resolvedMetrics) if nextReconcileTime != nil { enqueueSeconds := nextReconcileTime.Sub(time.Now()) if enqueueSeconds < 0 { @@ -93,6 +106,27 @@ func (c *Controller) reconcileAnalysisRun(origRun *v1alpha1.AnalysisRun) *v1alph return run } +func getResolvedMetricsWithoutSecrets(metrics []v1alpha1.Metric, args []v1alpha1.Argument) ([]v1alpha1.Metric, error) { + newArgs := make([]v1alpha1.Argument, 0) + for _, arg := range args { + newArg := arg.DeepCopy() + if newArg.ValueFrom != nil && newArg.ValueFrom.SecretKeyRef != nil { + newArg.ValueFrom = nil + newArg.Value = pointer.StringPtr("temp-for-secret") + } + newArgs = append(newArgs, *newArg) + } + resolvedMetrics := make([]v1alpha1.Metric, 0) + for _, metric := range metrics { + resolvedMetric, err := analysisutil.ResolveMetricArgs(metric, newArgs) + if err != nil { + return nil, err + } + resolvedMetrics = append(resolvedMetrics, *resolvedMetric) + } + return resolvedMetrics, nil +} + func (c *Controller) recordAnalysisRunCompletionEvent(run *v1alpha1.AnalysisRun) { eventType := corev1.EventTypeNormal switch run.Status.Phase { @@ -106,11 +140,12 @@ func (c *Controller) recordAnalysisRunCompletionEvent(run *v1alpha1.AnalysisRun) // sync, based on the last completion times that metric was measured (if ever). If the run is // terminating (e.g. due to manual termination or failing metric), will not schedule further // measurements other than to resume any in-flight measurements. -func generateMetricTasks(run *v1alpha1.AnalysisRun) []metricTask { +func generateMetricTasks(run *v1alpha1.AnalysisRun, metrics []v1alpha1.Metric) []metricTask { log := logutil.WithAnalysisRun(run) var tasks []metricTask terminating := analysisutil.IsTerminating(run) - for _, metric := range run.Spec.Metrics { + + for i, metric := range metrics { if analysisutil.MetricCompleted(run, metric.Name) { continue } @@ -124,7 +159,7 @@ func generateMetricTasks(run *v1alpha1.AnalysisRun) []metricTask { // last measurement is still in-progress. need to complete it logCtx.Infof("resuming in-progress measurement") tasks = append(tasks, metricTask{ - metric: metric, + metric: run.Spec.Metrics[i], incompleteMeasurement: lastMeasurement, }) continue @@ -149,7 +184,7 @@ func generateMetricTasks(run *v1alpha1.AnalysisRun) []metricTask { } } // measurement never taken - tasks = append(tasks, metricTask{metric: metric}) + tasks = append(tasks, metricTask{metric: run.Spec.Metrics[i]}) logCtx.Infof("running initial measurement") continue } @@ -174,7 +209,7 @@ func generateMetricTasks(run *v1alpha1.AnalysisRun) []metricTask { interval = metricInterval } if time.Now().After(lastMeasurement.FinishedAt.Add(interval)) { - tasks = append(tasks, metricTask{metric: metric}) + tasks = append(tasks, metricTask{metric: run.Spec.Metrics[i]}) logCtx.Infof("running overdue measurement") continue } @@ -238,7 +273,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa var resultsLock sync.Mutex terminating := analysisutil.IsTerminating(run) - // resolve args for metricTasks + // resolve args for metric tasks // get list of secret values for log redaction tasks, secrets, err := c.resolveArgs(tasks, run.Spec.Args, run.Namespace) if err != nil { @@ -345,7 +380,7 @@ func (c *Controller) runMeasurements(run *v1alpha1.AnalysisRun, tasks []metricTa // assessRunStatus assesses the overall status of this AnalysisRun // If any metric is not yet completed, the AnalysisRun is still considered Running // Once all metrics are complete, the worst status is used as the overall AnalysisRun status -func (c *Controller) assessRunStatus(run *v1alpha1.AnalysisRun) (v1alpha1.AnalysisPhase, string) { +func (c *Controller) assessRunStatus(run *v1alpha1.AnalysisRun, metrics []v1alpha1.Metric) (v1alpha1.AnalysisPhase, string) { var worstStatus v1alpha1.AnalysisPhase var worstMessage string terminating := analysisutil.IsTerminating(run) @@ -360,7 +395,7 @@ func (c *Controller) assessRunStatus(run *v1alpha1.AnalysisRun) (v1alpha1.Analys } // Iterate all metrics and update MetricResult.Phase fields based on latest measurement(s) - for _, metric := range run.Spec.Metrics { + for _, metric := range metrics { if result := analysisutil.GetResult(run, metric.Name); result != nil { log := logutil.WithAnalysisRun(run).WithField("metric", metric.Name) metricStatus := assessMetricStatus(metric, *result, terminating) @@ -497,9 +532,9 @@ func assessMetricFailureInconclusiveOrError(metric v1alpha1.Metric, result v1alp // calculateNextReconcileTime calculates the next time that this AnalysisRun should be reconciled, // based on the earliest time of all metrics intervals, counts, and their finishedAt timestamps -func calculateNextReconcileTime(run *v1alpha1.AnalysisRun) *time.Time { +func calculateNextReconcileTime(run *v1alpha1.AnalysisRun, metrics []v1alpha1.Metric) *time.Time { var reconcileTime *time.Time - for _, metric := range run.Spec.Metrics { + for _, metric := range metrics { if analysisutil.MetricCompleted(run, metric.Name) { // NOTE: this also covers the case where metric.Count is reached continue diff --git a/analysis/analysis_test.go b/analysis/analysis_test.go index c901984e35..5b1fa56062 100644 --- a/analysis/analysis_test.go +++ b/analysis/analysis_test.go @@ -173,7 +173,7 @@ func TestGenerateMetricTasksInterval(t *testing.T) { } { // ensure we don't take measurements when within the interval - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) } { @@ -182,7 +182,7 @@ func TestGenerateMetricTasksInterval(t *testing.T) { successRate.Measurements[0].StartedAt = timePtr(metav1.NewTime(time.Now().Add(-61 * time.Second))) successRate.Measurements[0].FinishedAt = timePtr(metav1.NewTime(time.Now().Add(-61 * time.Second))) run.Status.MetricResults[0] = successRate - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) } } @@ -208,11 +208,11 @@ func TestGenerateMetricTasksFailing(t *testing.T) { }, } // ensure we don't perform more measurements when one result already failed - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) run.Status.MetricResults = nil // ensure we schedule tasks when no results are failed - tasks = generateMetricTasks(run) + tasks = generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 2, len(tasks)) } @@ -239,7 +239,7 @@ func TestGenerateMetricTasksNoIntervalOrCount(t *testing.T) { } { // ensure we don't take measurement when result count indicates we completed - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) } { @@ -248,7 +248,7 @@ func TestGenerateMetricTasksNoIntervalOrCount(t *testing.T) { successRate.Measurements = nil successRate.Count = 0 run.Status.MetricResults[0] = successRate - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) } } @@ -275,7 +275,7 @@ func TestGenerateMetricTasksIncomplete(t *testing.T) { } { // ensure we don't take measurement when interval is not specified and we already took measurement - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) assert.NotNil(t, tasks[0].incompleteMeasurement) } @@ -300,25 +300,25 @@ func TestGenerateMetricTasksHonorInitialDelay(t *testing.T) { } { // ensure we don't take measurement for metrics with start delays when no startAt is set - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) } { run.Status.StartedAt = &nowMinus10 // ensure we don't take measurement for metrics with start delays where we haven't waited the start delay - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) } { run.Status.StartedAt = &nowMinus20 // ensure we do take measurement for metrics with start delays where we have waited the start delay - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) } { run.Spec.Metrics[0].InitialDelay = "invalid-start-delay" // ensure we don't take measurement for metrics with invalid start delays - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 0, len(tasks)) } } @@ -366,7 +366,7 @@ func TestGenerateMetricTasksHonorResumeAt(t *testing.T) { } { // ensure we don't take measurement when resumeAt has not passed - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) assert.Equal(t, "success-rate2", tasks[0].metric.Name) } @@ -397,13 +397,13 @@ func TestGenerateMetricTasksError(t *testing.T) { } { run := run.DeepCopy() - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) } { run := run.DeepCopy() run.Spec.Metrics[0].Interval = "5m" - tasks := generateMetricTasks(run) + tasks := generateMetricTasks(run, run.Spec.Metrics) assert.Equal(t, 1, len(tasks)) } } @@ -439,7 +439,7 @@ func TestAssessRunStatus(t *testing.T) { }, }, } - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) assert.Equal(t, v1alpha1.AnalysisPhaseRunning, status) assert.Equal(t, "", message) } @@ -458,7 +458,7 @@ func TestAssessRunStatus(t *testing.T) { }, }, } - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status) assert.Equal(t, "", message) } @@ -512,7 +512,7 @@ func TestAssessRunStatusUpdateResult(t *testing.T) { }, }, } - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) assert.Equal(t, v1alpha1.AnalysisPhaseRunning, status) assert.Equal(t, "", message) assert.Equal(t, v1alpha1.AnalysisPhaseFailed, run.Status.MetricResults[1].Phase) @@ -671,11 +671,11 @@ func TestCalculateNextReconcileTimeInterval(t *testing.T) { }, } // ensure we requeue at correct interval - assert.Equal(t, now.Add(time.Second*30), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(time.Second*30), *calculateNextReconcileTime(run, run.Spec.Metrics)) // when in-flight is not set, we do not requeue run.Status.MetricResults[0].Measurements[0].FinishedAt = nil run.Status.MetricResults[0].Measurements[0].Phase = v1alpha1.AnalysisPhaseRunning - assert.Nil(t, calculateNextReconcileTime(run)) + assert.Nil(t, calculateNextReconcileTime(run, run.Spec.Metrics)) // do not queue completed metrics nowMinus120 := metav1.NewTime(now.Add(time.Second * -120)) run.Status.MetricResults[0] = v1alpha1.MetricResult{ @@ -687,7 +687,7 @@ func TestCalculateNextReconcileTimeInterval(t *testing.T) { FinishedAt: &nowMinus120, }}, } - assert.Nil(t, calculateNextReconcileTime(run)) + assert.Nil(t, calculateNextReconcileTime(run, run.Spec.Metrics)) } func TestCalculateNextReconcileTimeInitialDelay(t *testing.T) { @@ -723,10 +723,10 @@ func TestCalculateNextReconcileTimeInitialDelay(t *testing.T) { }, } // ensure we requeue after start delay - assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run, run.Spec.Metrics)) run.Spec.Metrics[1].InitialDelay = "not-valid-start-delay" // skip invalid start delay and use the other metrics next reconcile time - assert.Equal(t, now.Add(time.Second*30), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(time.Second*30), *calculateNextReconcileTime(run, run.Spec.Metrics)) } @@ -754,7 +754,7 @@ func TestCalculateNextReconcileTimeNoInterval(t *testing.T) { }}, }, } - assert.Nil(t, calculateNextReconcileTime(run)) + assert.Nil(t, calculateNextReconcileTime(run, run.Spec.Metrics)) } func TestCalculateNextReconcileEarliestMetric(t *testing.T) { @@ -801,7 +801,7 @@ func TestCalculateNextReconcileEarliestMetric(t *testing.T) { }, } // ensure we requeue at correct interval - assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run, run.Spec.Metrics)) } func TestCalculateNextReconcileHonorResumeAt(t *testing.T) { @@ -830,7 +830,7 @@ func TestCalculateNextReconcileHonorResumeAt(t *testing.T) { }, } // ensure we requeue at correct interval - assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(time.Second*10), *calculateNextReconcileTime(run, run.Spec.Metrics)) } // TestCalculateNextReconcileUponError ensure we requeue at error interval when we error @@ -860,12 +860,12 @@ func TestCalculateNextReconcileUponError(t *testing.T) { } { run := run.DeepCopy() - assert.Equal(t, now.Add(DefaultErrorRetryInterval), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(DefaultErrorRetryInterval), *calculateNextReconcileTime(run, run.Spec.Metrics)) } { run := run.DeepCopy() run.Spec.Metrics[0].Interval = "5m" - assert.Equal(t, now.Add(DefaultErrorRetryInterval), *calculateNextReconcileTime(run)) + assert.Equal(t, now.Add(DefaultErrorRetryInterval), *calculateNextReconcileTime(run, run.Spec.Metrics)) } } @@ -1083,8 +1083,8 @@ func TestResolveMetricArgsUnableToSubstitute(t *testing.T) { }, } newRun := c.reconcileAnalysisRun(run) - assert.Equal(t, newRun.Status.Phase, v1alpha1.AnalysisPhaseError) - assert.Equal(t, newRun.Status.Message, "unable to resolve metric arguments: failed to resolve {{args.metric-name}}") + assert.Equal(t, v1alpha1.AnalysisPhaseError, newRun.Status.Phase) + assert.Equal(t, "unable to resolve metric arguments: failed to resolve {{args.metric-name}}", newRun.Status.Message) } // TestSecretContentReferenceSuccess verifies that secret arguments are properly resolved @@ -1403,7 +1403,7 @@ func TestAssessRunStatusErrorMessageAnalysisPhaseFail(t *testing.T) { run := newTerminatingRun(v1alpha1.AnalysisPhaseFailed) run.Status.MetricResults[0].Phase = v1alpha1.AnalysisPhaseSuccessful - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status) assert.Equal(t, "metric \"failed-metric\" assessed Failed due to failed (1) > failureLimit (0)", message) } @@ -1421,7 +1421,7 @@ func TestAssessRunStatusErrorMessageFromProvider(t *testing.T) { providerMessage := "Provider error" run.Status.MetricResults[1].Message = providerMessage - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) expectedMessage := fmt.Sprintf("metric \"failed-metric\" assessed Failed due to failed (1) > failureLimit (0): \"Error Message: %s\"", providerMessage) assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status) assert.Equal(t, expectedMessage, message) @@ -1438,7 +1438,7 @@ func TestAssessRunStatusMultipleFailures(t *testing.T) { run.Status.MetricResults[0].Phase = v1alpha1.AnalysisPhaseFailed run.Status.MetricResults[0].Failed = 1 - status, message := c.assessRunStatus(run) + status, message := c.assessRunStatus(run, run.Spec.Metrics) assert.Equal(t, v1alpha1.AnalysisPhaseFailed, status) assert.Equal(t, "metric \"run-forever\" assessed Failed due to failed (1) > failureLimit (0)", message) } diff --git a/test/e2e/analysis_test.go b/test/e2e/analysis_test.go index f0a9758a1a..c0922ad7c5 100644 --- a/test/e2e/analysis_test.go +++ b/test/e2e/analysis_test.go @@ -4,10 +4,12 @@ package e2e import ( "fmt" + "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "testing" "time" "github.com/stretchr/testify/suite" + "github.com/tj/assert" "github.com/argoproj/argo-rollouts/test/fixtures" ) @@ -625,7 +627,7 @@ spec: } func (s *AnalysisSuite) TestAnalysisWithSecret() { - (s.Given(). + s.Given(). RolloutObjects("@functional/rollout-secret.yaml"). When(). ApplyManifests(). @@ -636,11 +638,63 @@ func (s *AnalysisSuite) TestAnalysisWithSecret() { UpdateSpec(). WaitForRolloutStatus("Paused"). Then(). - ExpectAnalysisRunCount(1). + Assert(func(t *fixtures.Then) { + ar := t.GetRolloutAnalysisRuns().Items[0] + assert.Equal(s.T(), v1alpha1.AnalysisPhaseSuccessful, ar.Status.Phase) + metricResult := ar.Status.MetricResults[0] + assert.Equal(s.T(), int32(2), metricResult.Count) + }). When(). WaitForInlineAnalysisRunPhase("Successful"). PromoteRollout(). WaitForRolloutStatus("Healthy"). Then(). - ExpectStableRevision("2")) + ExpectStableRevision("2") } + + +func (s *AnalysisSuite) TestAnalysisWithArgs() { + s.Given(). + RolloutObjects("@functional/rollout-secret-withArgs.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectAnalysisRunCount(0). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Then(). + Assert(func(t *fixtures.Then) { + ar := t.GetRolloutAnalysisRuns().Items[0] + assert.Equal(s.T(), v1alpha1.AnalysisPhaseSuccessful, ar.Status.Phase) + metricResult := ar.Status.MetricResults[0] + assert.Equal(s.T(), int32(3), metricResult.Count) + }). + When(). + WaitForInlineAnalysisRunPhase("Successful"). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectStableRevision("2") +} + +func (s *AnalysisSuite) TestBackgroundAnalysisWithArgs() { + s.Given(). + RolloutObjects("@functional/rollout-bg-analysis-withArgs.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + ExpectAnalysisRunCount(0). + When(). + UpdateSpec(). + WaitForRolloutStatus("Paused"). + Then(). + ExpectAnalysisRunCount(1). + ExpectBackgroundAnalysisRunPhase("Running"). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + WaitForBackgroundAnalysisRunPhase("Successful") +} \ No newline at end of file diff --git a/test/e2e/functional/analysistemplate-web-background.yaml b/test/e2e/functional/analysistemplate-web-background.yaml index f76a96f801..8f4fa90571 100644 --- a/test/e2e/functional/analysistemplate-web-background.yaml +++ b/test/e2e/functional/analysistemplate-web-background.yaml @@ -4,11 +4,14 @@ kind: AnalysisTemplate metadata: name: web-background spec: + args: + - name: url-val + value: "https://kubernetes.default.svc/version" metrics: - name: web interval: 5s successCondition: result.major == '1' provider: web: - url: https://kubernetes.default.svc/version + url: "{{args.url-val}}" insecure: true diff --git a/test/e2e/functional/rollout-bg-analysis-withArgs.yaml b/test/e2e/functional/rollout-bg-analysis-withArgs.yaml new file mode 100644 index 0000000000..5cd2f5ba65 --- /dev/null +++ b/test/e2e/functional/rollout-bg-analysis-withArgs.yaml @@ -0,0 +1,74 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-bg-analysis +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: rollout-bg-analysis + template: + metadata: + labels: + app: rollout-bg-analysis + spec: + containers: + - name: rollouts-demo + image: nginx:1.19-alpine + imagePullPolicy: Always + ports: + - containerPort: 8080 + strategy: + canary: + analysis: + templates: + - templateName: analysis-secret + startingStep: 1 + args: + - name: failure-limit + value: "2" + - name: analysis-interval + value: "10s" + - name: inconclusive-limit + value: "2" + - name: success-condition + value: "It worked!" + steps: + - setWeight: 25 + - pause: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-secret +type: Opaque +data: + secretUrl: aHR0cHM6Ly9naXN0LmdpdGh1YnVzZXJjb250ZW50LmNvbS9raGhpcmFuaS8yYWIxMTIzMjQwMjUxOGQ1Mjc3YWYwMzBkZDg5MTZkNy9yYXcvZDI3MmY1NTFmMmQxODA2YTAzOTc0ZGJhZWYxMWRmZDU1MTAyZmVlYS9leGFtcGxlLmpzb24= +--- +kind: AnalysisTemplate +apiVersion: argoproj.io/v1alpha1 +metadata: + name: analysis-secret +spec: + args: + - name: analysis-interval + - name: failure-limit + - name: inconclusive-limit + - name: success-condition + value: "replace" + - name: secret-url + valueFrom: + secretKeyRef: + name: example-secret + key: secretUrl + metrics: + - name: webmetric + interval: "{{args.analysis-interval}}" + initialDelay: "{{args.analysis-interval}}" + failureLimit: "{{args.failure-limit}}" + successCondition: result == "{{args.success-condition}}" + provider: + web: + url: "{{args.secret-url}}" + jsonPath: "{$.message}" diff --git a/test/e2e/functional/rollout-secret-withArgs.yaml b/test/e2e/functional/rollout-secret-withArgs.yaml new file mode 100644 index 0000000000..16da5da049 --- /dev/null +++ b/test/e2e/functional/rollout-secret-withArgs.yaml @@ -0,0 +1,78 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: rollout-secret +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: rollout-secret + template: + metadata: + labels: + app: rollout-secret + spec: + containers: + - name: rollouts-demo + image: nginx:1.19-alpine + imagePullPolicy: Always + ports: + - containerPort: 8080 + strategy: + canary: + steps: + - setWeight: 25 + - analysis: + templates: + - templateName: analysis-secret-args + args: + - name: failure-limit + value: "2" + - name: analysis-interval + value: "5s" + - name: analysis-runs + value: "3" + - name: inconclusive-limit + value: "2" + - name: success-condition + value: "It worked!" + - pause: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: example-secret +type: Opaque +data: + secretUrl: aHR0cHM6Ly9naXN0LmdpdGh1YnVzZXJjb250ZW50LmNvbS9raGhpcmFuaS8yYWIxMTIzMjQwMjUxOGQ1Mjc3YWYwMzBkZDg5MTZkNy9yYXcvZDI3MmY1NTFmMmQxODA2YTAzOTc0ZGJhZWYxMWRmZDU1MTAyZmVlYS9leGFtcGxlLmpzb24= +--- +kind: AnalysisTemplate +apiVersion: argoproj.io/v1alpha1 +metadata: + name: analysis-secret-args +spec: + args: + - name: analysis-interval + value: "100s" + - name: analysis-runs + value: "10" + - name: failure-limit + - name: inconclusive-limit + - name: success-condition + - name: secret-url + valueFrom: + secretKeyRef: + name: example-secret + key: secretUrl + metrics: + - name: webmetric + count: "{{args.analysis-runs}}" + interval: "{{args.analysis-interval}}" + initialDelay: "{{args.analysis-interval}}" + failureLimit: "{{args.failure-limit}}" + successCondition: result == "{{args.success-condition}}" + provider: + web: + url: "{{args.secret-url}}" + jsonPath: "{$.message}" diff --git a/test/e2e/functional/rollout-secret.yaml b/test/e2e/functional/rollout-secret.yaml index 42db8000f2..b584f9f90e 100644 --- a/test/e2e/functional/rollout-secret.yaml +++ b/test/e2e/functional/rollout-secret.yaml @@ -15,7 +15,7 @@ spec: spec: containers: - name: rollouts-demo - image: argoproj/rollouts-demo:blue + image: nginx:1.19-alpine imagePullPolicy: Always ports: - containerPort: 8080 @@ -42,6 +42,14 @@ metadata: name: analysis-secret spec: args: + - name: analysis-interval + value: "5s" + - name: analysis-runs + value: "2" + - name: failure-limit + value: "1" + - name: inconclusive-limit + value: "1" - name: secret-url valueFrom: secretKeyRef: @@ -49,6 +57,9 @@ spec: key: secretUrl metrics: - name: webmetric + count: "{{args.analysis-runs}}" + interval: "{{args.analysis-interval}}" + failureLimit: "{{args.failure-limit}}" successCondition: result == 'It worked!' provider: web: From 7bd455f70ad7fd544bea7b58b48dfd9f9074d11c Mon Sep 17 00:00:00 2001 From: Kareena Hirani Date: Wed, 25 Aug 2021 13:34:24 -0700 Subject: [PATCH 30/34] fix: Nginx ingressClassName passed to canary ingress (#1448) * fix: nginx ingressClassName passed to canary ingress Signed-off-by: khhirani --- rollout/trafficrouting/nginx/nginx.go | 5 +++++ rollout/trafficrouting/nginx/nginx_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index 333095560d..5054004b96 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -73,6 +73,11 @@ func (r *Reconciler) canaryIngress(stableIngress *extensionsv1beta1.Ingress, nam }, } + // Preserve ingressClassName from stable ingress + if stableIngress.Spec.IngressClassName != nil { + desiredCanaryIngress.Spec.IngressClassName = stableIngress.Spec.IngressClassName + } + // Must preserve ingress.class on canary ingress, no other annotations matter // See: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#canary if val, ok := stableIngress.Annotations["kubernetes.io/ingress.class"]; ok { diff --git a/rollout/trafficrouting/nginx/nginx_test.go b/rollout/trafficrouting/nginx/nginx_test.go index a747e34835..04349bba42 100644 --- a/rollout/trafficrouting/nginx/nginx_test.go +++ b/rollout/trafficrouting/nginx/nginx_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + "k8s.io/utils/pointer" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" @@ -94,6 +96,7 @@ func TestCanaryIngressCreate(t *testing.T) { }, } stableIngress := ingress("stable-ingress", 80, "stable-service") + stableIngress.Spec.IngressClassName = pointer.StringPtr("nginx-ext") desiredCanaryIngress, err := r.canaryIngress(stableIngress, ingressutil.GetCanaryIngressName(r.cfg.Rollout), 10) assert.Nil(t, err, "No error returned when calling canaryIngress") @@ -101,6 +104,8 @@ func TestCanaryIngressCreate(t *testing.T) { checkBackendService(t, desiredCanaryIngress, "canary-service") assert.Equal(t, "true", desiredCanaryIngress.Annotations["nginx.ingress.kubernetes.io/canary"], "canary annotation set to true") assert.Equal(t, "10", desiredCanaryIngress.Annotations["nginx.ingress.kubernetes.io/canary-weight"], "canary-weight annotation set to expected value") + assert.NotNil(t, desiredCanaryIngress.Spec.IngressClassName) + assert.Equal(t, "nginx-ext", *desiredCanaryIngress.Spec.IngressClassName) } func TestCanaryIngressPatchWeight(t *testing.T) { From ea772f795c94745d625098108c4f797f326ea9de Mon Sep 17 00:00:00 2001 From: harikrongali <81331774+harikrongali@users.noreply.github.com> Date: Wed, 25 Aug 2021 22:17:19 -0700 Subject: [PATCH 31/34] fix: replica count for new deployment (#1449) fix: replica count for new deployment (#1449) Signed-off-by: hari rongali --- utils/replicaset/canary.go | 3 +-- utils/replicaset/canary_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/utils/replicaset/canary.go b/utils/replicaset/canary.go index 2068e4b983..6f89d184c6 100644 --- a/utils/replicaset/canary.go +++ b/utils/replicaset/canary.go @@ -187,12 +187,11 @@ func CalculateReplicaCountsForCanary(rollout *v1alpha1.Rollout, newRS *appsv1.Re } scaleDownCount := replicasToScaleDown - minAvailableReplicaCount - if !isIncreasing { // Skip scalingDown Stable replicaSet when Canary availability is not taken into calculation for scaleDown newRSReplicaCount = calculateScaleDownReplicaCount(newRS, desiredNewRSReplicaCount, scaleDownCount, newRSReplicaCount) newRSReplicaCount, stableRSReplicaCount = adjustReplicaWithinLimits(newRS, stableRS, newRSReplicaCount, stableRSReplicaCount, maxReplicaCountAllowed, minAvailableReplicaCount) - } else { + } else if scaleStableRS { // Skip scalingDown canary replicaSet when StableSet availability is not taken into calculation for scaleDown stableRSReplicaCount = calculateScaleDownReplicaCount(stableRS, desiredStableRSReplicaCount, scaleDownCount, stableRSReplicaCount) stableRSReplicaCount, newRSReplicaCount = adjustReplicaWithinLimits(stableRS, newRS, stableRSReplicaCount, newRSReplicaCount, maxReplicaCountAllowed, minAvailableReplicaCount) diff --git a/utils/replicaset/canary_test.go b/utils/replicaset/canary_test.go index 28e3f3e3c3..21b93a9d26 100644 --- a/utils/replicaset/canary_test.go +++ b/utils/replicaset/canary_test.go @@ -643,6 +643,15 @@ func TestCalculateReplicaCountsForCanary(t *testing.T) { } } +func TestCalculateReplicaCountsForNewDeployment(t *testing.T) { + rollout := newRollout(10, 10, intstr.FromInt(0), intstr.FromInt(1), "canary", "stable", nil, nil) + stableRS := newRS("stable", 10, 0) + newRS := newRS("stable", 10, 0) + newRSReplicaCount, stableRSReplicaCount := CalculateReplicaCountsForCanary(rollout, newRS, stableRS, nil) + assert.Equal(t, int32(10), newRSReplicaCount) + assert.Equal(t, int32(0), stableRSReplicaCount) +} + func TestCalculateReplicaCountsForCanaryTrafficRouting(t *testing.T) { rollout := newRollout(10, 10, intstr.FromInt(0), intstr.FromInt(1), "canary", "stable", nil, nil) rollout.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{} From 1cb9232d093e82c1fa12c568e1d44507034faa43 Mon Sep 17 00:00:00 2001 From: c1_zh <5362164+cezhang@users.noreply.github.com> Date: Thu, 26 Aug 2021 16:12:38 +0800 Subject: [PATCH 32/34] docs: fix typo in installation.md (#1450) Signed-off-by: cezhang --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 63d1f53479..9c8a2d88d5 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -10,7 +10,7 @@ kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/rele This will create a new namespace, `argo-rollouts`, where Argo Rollouts controller will run. !!! tip - If you are using another namspace name, please update `install.yall` clusterrolebinding's serviceaccount namespace name. + If you are using another namspace name, please update `install.yaml` clusterrolebinding's serviceaccount namespace name. !!! tip When installing Argo Rollouts on Kubernetes v1.14 or lower, the CRD manifests must be kubectl applied with the --validate=false option. This is caused by use of new CRD fields introduced in v1.15, which are rejected by default in lower API servers. From 2565c87942d2236c171495a0cfdb6c1675fc3b67 Mon Sep 17 00:00:00 2001 From: Jesse Suen Date: Thu, 26 Aug 2021 13:07:01 -0700 Subject: [PATCH 33/34] feat: verify AWS TargetGroup after updating active/stable services (#1348) Signed-off-by: Jesse Suen --- cmd/rollouts-controller/main.go | 53 +- docs/features/traffic-management/alb.md | 113 +++- experiments/replicaset.go | 2 +- ingress/alb.go | 2 +- manifests/install.yaml | 16 +- manifests/namespace-install.yaml | 16 +- manifests/role/argo-rollouts-clusterrole.yaml | 34 +- rollout/analysis.go | 8 +- rollout/analysis_test.go | 2 - rollout/bluegreen.go | 46 +- rollout/bluegreen_test.go | 44 +- rollout/canary.go | 65 +- rollout/context.go | 11 +- rollout/controller_test.go | 30 +- rollout/ephemeralmetadata_test.go | 1 - rollout/replicaset.go | 38 +- rollout/replicaset_test.go | 2 + rollout/service.go | 110 ++++ rollout/service_test.go | 568 ++++++++++++++++++ rollout/sync.go | 13 +- rollout/sync_test.go | 1 - rollout/trafficrouting.go | 17 +- rollout/trafficrouting/alb/alb.go | 25 +- rollout/trafficrouting/alb/alb_test.go | 9 +- .../trafficrouting/ambassador/ambassador.go | 13 +- .../ambassador/ambassador_test.go | 11 +- rollout/trafficrouting/smi/smi.go | 12 +- rollout/trafficrouting/smi/smi_test.go | 44 +- rollout/trafficrouting_test.go | 23 +- test/e2e/aws_test.go | 19 +- test/e2e/canary_test.go | 1 + .../e2e/functional/alb-bluegreen-rollout.yaml | 71 +++ ...b-rollout.yaml => alb-canary-rollout.yaml} | 34 +- test/fixtures/e2e_suite.go | 2 + test/fixtures/when.go | 21 +- test/util/util.go | 10 +- utils/aws/aws.go | 251 +++++++- utils/aws/aws_test.go | 236 +++++++- utils/aws/mocks/ELBv2APIClient.go | 30 + utils/conditions/conditions.go | 12 + utils/defaults/defaults.go | 59 +- utils/defaults/defaults_test.go | 27 + utils/istio/istio.go | 16 +- utils/istio/istio_test.go | 5 +- utils/rollout/rolloututil.go | 29 +- utils/rollout/rolloututil_test.go | 35 ++ 46 files changed, 1895 insertions(+), 292 deletions(-) create mode 100644 test/e2e/functional/alb-bluegreen-rollout.yaml rename test/e2e/functional/{alb-rollout.yaml => alb-canary-rollout.yaml} (71%) diff --git a/cmd/rollouts-controller/main.go b/cmd/rollouts-controller/main.go index 67f08c2baf..f07726e762 100644 --- a/cmd/rollouts-controller/main.go +++ b/cmd/rollouts-controller/main.go @@ -24,9 +24,6 @@ import ( jobprovider "github.com/argoproj/argo-rollouts/metricproviders/job" clientset "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned" "github.com/argoproj/argo-rollouts/pkg/signals" - "github.com/argoproj/argo-rollouts/rollout/trafficrouting/alb" - "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" - "github.com/argoproj/argo-rollouts/rollout/trafficrouting/smi" controllerutil "github.com/argoproj/argo-rollouts/utils/controller" "github.com/argoproj/argo-rollouts/utils/defaults" istioutil "github.com/argoproj/argo-rollouts/utils/istio" @@ -43,25 +40,25 @@ const ( func newCommand() *cobra.Command { var ( - clientConfig clientcmd.ClientConfig - rolloutResyncPeriod int64 - logLevel string - klogLevel int - metricsPort int - instanceID string - rolloutThreads int - experimentThreads int - analysisThreads int - serviceThreads int - ingressThreads int - istioVersion string - trafficSplitVersion string - ambassadorVersion string - albIngressClasses []string - nginxIngressClasses []string - albVerifyWeight bool - namespaced bool - printVersion bool + clientConfig clientcmd.ClientConfig + rolloutResyncPeriod int64 + logLevel string + klogLevel int + metricsPort int + instanceID string + rolloutThreads int + experimentThreads int + analysisThreads int + serviceThreads int + ingressThreads int + istioVersion string + trafficSplitVersion string + ambassadorVersion string + albIngressClasses []string + nginxIngressClasses []string + awsVerifyTargetGroup bool + namespaced bool + printVersion bool ) var command = cobra.Command{ Use: cliName, @@ -82,10 +79,10 @@ func newCommand() *cobra.Command { // set up signals so we handle the first shutdown signal gracefully stopCh := signals.SetupSignalHandler() - alb.SetDefaultVerifyWeight(albVerifyWeight) - istioutil.SetIstioAPIVersion(istioVersion) - ambassador.SetAPIVersion(ambassadorVersion) - smi.SetSMIAPIVersion(trafficSplitVersion) + defaults.SetVerifyTargetGroup(awsVerifyTargetGroup) + defaults.SetIstioAPIVersion(istioVersion) + defaults.SetAmbassadorAPIVersion(ambassadorVersion) + defaults.SetSMIAPIVersion(trafficSplitVersion) config, err := clientConfig.ClientConfig() checkError(err) @@ -216,7 +213,9 @@ func newCommand() *cobra.Command { command.Flags().StringVar(&trafficSplitVersion, "traffic-split-api-version", defaults.DefaultSMITrafficSplitVersion, "Set the default TrafficSplit apiVersion that controller uses when creating TrafficSplits.") command.Flags().StringArrayVar(&albIngressClasses, "alb-ingress-classes", defaultALBIngressClass, "Defines all the ingress class annotations that the alb ingress controller operates on. Defaults to alb") command.Flags().StringArrayVar(&nginxIngressClasses, "nginx-ingress-classes", defaultNGINXIngressClass, "Defines all the ingress class annotations that the nginx ingress controller operates on. Defaults to nginx") - command.Flags().BoolVar(&albVerifyWeight, "alb-verify-weight", false, "Verify ALB target group weights before progressing through steps (requires AWS privileges)") + command.Flags().BoolVar(&awsVerifyTargetGroup, "alb-verify-weight", false, "Verify ALB target group weights before progressing through steps (requires AWS privileges)") + command.Flags().MarkDeprecated("alb-verify-weight", "Use --aws-verify-target-group instead") + command.Flags().BoolVar(&awsVerifyTargetGroup, "aws-verify-target-group", false, "Verify ALB target group before progressing through steps (requires AWS privileges)") command.Flags().BoolVar(&printVersion, "version", false, "Print version") return &command } diff --git a/docs/features/traffic-management/alb.md b/docs/features/traffic-management/alb.md index ef614573c7..7f9e8e031e 100644 --- a/docs/features/traffic-management/alb.md +++ b/docs/features/traffic-management/alb.md @@ -158,24 +158,107 @@ spec: ... ``` -### Weight verification +### Zero-Downtime Updates with AWS TargetGroup Verification + +Argo Rollouts contains two features to help ensure zero-downtime updates when used with the AWS +LoadBalancer controller: TargetGroup IP verification and TargetGroup weight verification. Both +features involve the Rollout controller performing additional safety checks to AWS, to verify +the changes made to the Ingress object are reflected in the underlying AWS TargetGroup. + +#### TargetGroup IP Verification + +!!! note + + Target Group IP verification available since Argo Rollouts v1.1 + +The AWS LoadBalancer controller can run in one of two modes: +* [Instance mode](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/how-it-works/#instance-mode) +* [IP mode](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/how-it-works/#ip-mode) + +TargetGroup IP Verification is only applicable when the AWS LoadBalancer controller in IP mode. +When using the AWS LoadBalancer controller in IP mode (e.g. using the AWS CNI), the ALB LoadBalancer +targets individual Pod IPs, as opposed to K8s node instances. Targeting Pod IPs comes with an +increased risk of downtime during an update, because the Pod IPs behind the underlying AWS TargetGroup +can more easily become outdated from the *_actual_* availability and status of pods, causing HTTP 502 +errors when the TargetGroup points to pods which have already been scaled down. + +To mitigate this risk, AWS recommends the use of +[pod readiness gate injection](https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/deploy/pod_readiness_gate/) +when running the AWS LoadBalancer in IP mode. Readiness gates allow for the AWS LoadBalancer +controller to verify that TargetGroups are accurate before marking newly created Pods as "ready", +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: +* 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 + +Another way to describe this is: the AWS LoadBalancer controller injects readiness gates onto Pods +only if they are "reachable" from an ALB Ingress at the time of pod creation. A pod is considered +reachable if an (ALB) Ingress references a Service which matches the pod labels. It ignores all other Pods. + +One challenge with this manner of pod readiness gate injection, is that modifications to the Service +selector labels (`spec.selector`) do not allow for the AWS LoadBalancer controller to inject the +readiness gates, because by that time the Pod was already created (and readiness gates are immutable). +Note that this is an issue when you change Service selectors of *_any_* ALB Service, not just ones +involved in Argo Rollouts. + +Because Argo Rollout's blue-green strategy works by modifying the activeService selector to the new +ReplicaSet labels during promotion, it suffers from the issue where readiness gates for the +`spec.strategy.blueGreen.activeService` fail to be injected. This means there is a possibility of +downtime in the following problematic scenario during an update from V1 to V2: + +1. Update is triggered and V2 ReplicaSet stack is scaled up +2. V2 ReplicaSet pods become fully available and ready to be promoted +3. Rollout promotes V2 by updating the label selectors of the active service to point to the V2 stack (from V1) +4. Due to unknown issues (e.g. AWS load balancer controller downtime, AWS rate limiting), registration + of the V2 Pod IPs to the TargetGroup does not happen or is delayed. +5. V1 ReplicaSet is scaled down to complete the update + +After step 5, when the V1 ReplicaSet is scaled down, the outdated TargetGroup would still be pointing +to the V1 Pods IPs which no longer exist, causing downtime. + +To allow for zero-downtime updates, Argo Rollouts has the ability to perform TargetGroup IP +verification as an additional safety measure during an update. When this feature is enabled, whenever +a service selector modification is made, the Rollout controller blocks progression of the update +until it can verify the TargetGroup is accurately targeting the new Pod IPs of the +`bluegreen.activeService`. Verification is achieved by querying AWS APIs to describe the underlying +TargetGroup, iterating its registered IPs, and ensuring all Pod IPs of the activeService's +`Endpoints` list are registered in the TargetGroup. Verification must succeed before running +postPromotionAnalysis or scaling down the old ReplicaSet. + +Similarly for the canary strategy, after updating the `canary.stableService` selector labels to +point to the new ReplicaSet, the TargetGroup IP verification feature allows the controller to block +the scale down of the old ReplicaSet until it verifies the Pods IP behind the stableService +TargetGroup are accurate. + +#### TargetGroup Weight Verification !!! note - Since Argo Rollouts v1.0 + TargetGroup weight verification available since Argo Rollouts v1.0 + +TargetGroup weight verification addresses a similar problem to TargetGroup IP verification, but +instead of verifying that the Pod IPs of a service are reflected accurately in the TargetGroup, the +controller verifies that the traffic *_weights_* are accurate from what was set in the ingress +annotations. Weight verification is applicable to AWS LoadBalancer controllers which are running +either in IP mode or Instance mode. + +After Argo Rollouts adjusts a canary weight by updating the Ingress annotation, it moves on to the +next step. However, due to external factors (e.g. AWS rate limiting, AWS load balancer controller +downtime) it is possible that the weight modifications made to the Ingress, did not take effect in +the underlying TargetGroup. This is potentially dangerous as the controller will believe it is safe +to scale down the old stable stack when in reality, the outdated TargetGroup may still be pointing +to it. + +Using the TargetGroup weight verification feature, the rollout controller will additionally *verify* +the canary weight after a `setWeight` canary step. It accomplishes this by querying AWS LoadBalancer +APIs directly, to confirm that the Rules, Actions, and TargetGroups reflect the desire of Ingress +annotation. -When Argo Rollouts adjusts a canary weight by updating the Ingress annotation, it assumes that -the new weight immediately takes effect and moves on to the next step. However, due to external -factors (e.g. AWS rate limiting, AWS load balancer controller downtime) it is possible that the -ingress modification may take a long time to take effect (or possibly never even made). This is -potentially dangerous when the rollout completes its steps, it will scale down the old stack. If -the ALB Rules/Actions were still directing traffic to the old stack (because the weights never took -effect), then this would cause downtime to the service when the old stack was scaled down. +#### Usage -To mitigate this, the rollout controller has a feature to additionally *verify* the canary weight -after a `setWeight` canary step. It accomplishes this by querying AWS LoadBalancer APIs directly, -to confirm that the Rules, Actions, and TargetGroups reflect the desire of Ingress annotation. -To enable ALB weight verification, add `--alb-verify-weight` flag to the rollout-controller flags: +To enable AWS target group verification, add `--aws-verify-target-group` flag to the rollout-controller flags: ```yaml apiVersion: apps/v1 @@ -187,7 +270,8 @@ spec: spec: containers: - name: argo-rollouts - args: [--alb-verify-weight] + args: [--aws-verify-target-group] + # NOTE: in v1.0, the --alb-verify-weight flag should be used instead ``` For this feature to work, the argo-rollouts deployment requires the following AWS API permissions @@ -198,6 +282,7 @@ under the [Elastic Load Balancing API](https://docs.aws.amazon.com/elasticloadba * `DescribeListeners` * `DescribeRules` * `DescribeTags` +* `DescribeTargetHealth` There are various ways of granting AWS privileges to the argo-rollouts pods, which is highly dependent to your cluster's AWS environment, and out-of-scope of this documentation. Some solutions diff --git a/experiments/replicaset.go b/experiments/replicaset.go index 698d9cfe03..5d97eb15c1 100644 --- a/experiments/replicaset.go +++ b/experiments/replicaset.go @@ -231,7 +231,7 @@ func (ec *experimentContext) addScaleDownDelay(rs *appsv1.ReplicaSet) (bool, err patch := fmt.Sprintf(addScaleDownAtAnnotationsPatch, v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, deadline) _, err := ec.kubeclientset.AppsV1().ReplicaSets(rs.Namespace).Patch(ctx, rs.Name, patchtypes.JSONPatchType, []byte(patch), metav1.PatchOptions{}) if err == nil { - ec.log.Infof("Set '%s' annotation on '%s' to %s (%ds)", v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, rs.Name, deadline, scaleDownDelaySeconds) + ec.log.Infof("Set '%s' annotation on '%s' to %s (%s)", v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, rs.Name, deadline, scaleDownDelaySeconds) rsIsUpdated = true } return rsIsUpdated, err diff --git a/ingress/alb.go b/ingress/alb.go index 1fb9a33839..b3454d3d8d 100644 --- a/ingress/alb.go +++ b/ingress/alb.go @@ -40,7 +40,7 @@ func (c *Controller) syncALBIngress(ingress *extensionsv1beta1.Ingress, rollouts delete(managedActions, roName) resetALBAction, err := getResetALBActionStr(ingress, actionKey) if err != nil { - log.WithField(logutil.IngressKey, ingress.Name).WithField(logutil.NamespaceKey, ingress.Namespace).Error(err) + log.WithField(logutil.RolloutKey, roName).WithField(logutil.IngressKey, ingress.Name).WithField(logutil.NamespaceKey, ingress.Namespace).Error(err) return nil } newIngress.Annotations[actionKey] = resetALBAction diff --git a/manifests/install.yaml b/manifests/install.yaml index 9fe2b1648b..450bc75af7 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -12726,8 +12726,10 @@ rules: - patch - apiGroups: - getambassador.io + - x.getambassador.io resources: - mappings + - ambassadormappings verbs: - create - watch @@ -12736,16 +12738,18 @@ rules: - list - delete - apiGroups: - - x.getambassador.io + - "" resources: - - ambassadormappings + - endpoints verbs: - - create - - watch - get - - update +- apiGroups: + - elbv2.k8s.aws + resources: + - targetgroupbindings + verbs: - list - - delete + - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index c971be8405..a0d92de442 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -12726,8 +12726,10 @@ rules: - patch - apiGroups: - getambassador.io + - x.getambassador.io resources: - mappings + - ambassadormappings verbs: - create - watch @@ -12736,16 +12738,18 @@ rules: - list - delete - apiGroups: - - x.getambassador.io + - "" resources: - - ambassadormappings + - endpoints verbs: - - create - - watch - get - - update +- apiGroups: + - elbv2.k8s.aws + resources: + - targetgroupbindings + verbs: - list - - delete + - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/manifests/role/argo-rollouts-clusterrole.yaml b/manifests/role/argo-rollouts-clusterrole.yaml index 6162c4e7b6..a5f9a1ed8a 100644 --- a/manifests/role/argo-rollouts-clusterrole.yaml +++ b/manifests/role/argo-rollouts-clusterrole.yaml @@ -56,17 +56,17 @@ rules: - update - patch - delete - # deployments and podtemplates read access needed for workload reference support +# deployments and podtemplates read access needed for workload reference support - apiGroups: - - "" - - apps + - "" + - apps resources: - - deployments - - podtemplates + - deployments + - podtemplates verbs: - - get - - list - - watch + - get + - list + - watch # services patch needed to update selector of canary/stable/active/preview services - apiGroups: - "" @@ -159,10 +159,13 @@ rules: - get - update - patch +# ambassador access needed for Ambassador provider - apiGroups: - getambassador.io + - x.getambassador.io resources: - mappings + - ambassadormappings verbs: - create - watch @@ -170,14 +173,17 @@ rules: - update - list - delete +# Endpoints and TargetGroupBindings needed for ALB target group verification - apiGroups: - - x.getambassador.io + - "" resources: - - ambassadormappings + - endpoints verbs: - - create - - watch - get - - update +- apiGroups: + - elbv2.k8s.aws + resources: + - targetgroupbindings + verbs: - list - - delete + - get diff --git a/rollout/analysis.go b/rollout/analysis.go index 02a944c874..e495b07656 100644 --- a/rollout/analysis.go +++ b/rollout/analysis.go @@ -21,6 +21,7 @@ import ( logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" + rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" ) const ( @@ -176,7 +177,7 @@ func needsNewAnalysisRun(currentAr *v1alpha1.AnalysisRun, rollout *v1alpha1.Roll // emitAnalysisRunStatusChanges emits a Kubernetes event if the analysis run of that type has changed status func (c *rolloutContext) emitAnalysisRunStatusChanges(prevStatus *v1alpha1.RolloutAnalysisRunStatus, ar *v1alpha1.AnalysisRun, arType string) { - if ar != nil { + if ar != nil && ar.Status.Phase != "" { if prevStatus == nil || prevStatus.Name == ar.Name && prevStatus.Status != ar.Status.Phase { prevStatusStr := "NoPreviousStatus" if prevStatus != nil { @@ -287,7 +288,8 @@ func (c *rolloutContext) reconcilePostPromotionAnalysisRun() (*v1alpha1.Analysis } c.log.Info("Reconciling Post Promotion Analysis") - if skipPostPromotionAnalysisRun(c.rollout, c.newRS) { + // don't start post-promotion if we are not ready to, or we are still waiting for target verification + if skipPostPromotionAnalysisRun(c.rollout, c.newRS) || !c.areTargetsVerified() { err := c.cancelAnalysisRuns([]*v1alpha1.AnalysisRun{currentAr}) return currentAr, err } @@ -317,7 +319,7 @@ func (c *rolloutContext) reconcileBackgroundAnalysisRun() (*v1alpha1.AnalysisRun } // Do not create a background run if the rollout is completely rolled out, just created, before the starting step - if c.rollout.Status.StableRS == c.rollout.Status.CurrentPodHash || c.rollout.Status.StableRS == "" || c.rollout.Status.CurrentPodHash == "" || replicasetutil.BeforeStartingStep(c.rollout) { + if rolloututil.IsFullyPromoted(c.rollout) || c.rollout.Status.StableRS == "" || c.rollout.Status.CurrentPodHash == "" || replicasetutil.BeforeStartingStep(c.rollout) { return nil, nil } diff --git a/rollout/analysis_test.go b/rollout/analysis_test.go index e32aa5e982..2678c26b1a 100644 --- a/rollout/analysis_test.go +++ b/rollout/analysis_test.go @@ -1847,7 +1847,6 @@ func TestRolloutPrePromotionAnalysisSwitchServiceAfterSuccess(t *testing.T) { f.serviceLister = append(f.serviceLister, activeSvc) f.expectPatchServiceAction(activeSvc, rs2PodHash) - f.expectPatchReplicaSetAction(rs1) patchIndex := f.expectPatchRolloutActionWithPatch(r2, OnlyObservedGenerationPatch) f.run(getKey(r2, t)) patch := f.getPatchedRolloutWithoutConditions(patchIndex) @@ -1915,7 +1914,6 @@ func TestRolloutPrePromotionAnalysisHonorAutoPromotionSeconds(t *testing.T) { f.serviceLister = append(f.serviceLister, activeSvc) f.expectPatchServiceAction(activeSvc, rs2PodHash) - f.expectPatchReplicaSetAction(rs1) patchIndex := f.expectPatchRolloutActionWithPatch(r2, OnlyObservedGenerationPatch) f.run(getKey(r2, t)) patch := f.getPatchedRolloutWithoutConditions(patchIndex) diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index 8789666b41..ef6df170ee 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -52,6 +52,11 @@ func (c *rolloutContext) rolloutBlueGreen() error { return err } + err = c.awsVerifyTargetGroups(activeSvc) + if err != nil { + return err + } + err = c.reconcileAnalysisRuns() if err != nil { return err @@ -219,16 +224,23 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForBlueGreen(oldRSs []*appsv1.Re c.log.Warnf("Prevented inadvertent scaleDown of RS '%s'", targetRS.Name) continue } - + if *targetRS.Spec.Replicas == 0 { + // cannot scale down this ReplicaSet. + continue + } var desiredReplicaCount int32 - annotationedRSs, desiredReplicaCount = c.ScaleDownDelayHelper(targetRS, annotationedRSs, rolloutReplicas) + var err error + annotationedRSs, desiredReplicaCount, err = c.scaleDownDelayHelper(targetRS, annotationedRSs, rolloutReplicas) + if err != nil { + return false, err + } - if *(targetRS.Spec.Replicas) == desiredReplicaCount { - // at desired account + if *targetRS.Spec.Replicas == desiredReplicaCount { + // already at desired account, nothing to do continue } // Scale down. - _, _, err := c.scaleReplicaSetAndRecordEvent(targetRS, desiredReplicaCount) + _, _, err = c.scaleReplicaSetAndRecordEvent(targetRS, desiredReplicaCount) if err != nil { return false, err } @@ -238,30 +250,6 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForBlueGreen(oldRSs []*appsv1.Re return hasScaled, nil } -func (c *rolloutContext) ScaleDownDelayHelper(rs *appsv1.ReplicaSet, annotationedRSs int32, rolloutReplicas int32) (int32, int32) { - desiredReplicaCount := int32(0) - scaleDownRevisionLimit := GetScaleDownRevisionLimit(c.rollout) - if replicasetutil.HasScaleDownDeadline(rs) { - annotationedRSs++ - if annotationedRSs > scaleDownRevisionLimit { - c.log.Infof("At ScaleDownDelayRevisionLimit (%d) and scaling down the rest", scaleDownRevisionLimit) - } else { - remainingTime, err := replicasetutil.GetTimeRemainingBeforeScaleDownDeadline(rs) - if err != nil { - c.log.Warnf("%v", err) - } else if remainingTime != nil { - c.log.Infof("RS '%s' has not reached the scaleDownTime", rs.Name) - if *remainingTime < c.resyncPeriod { - c.enqueueRolloutAfter(c.rollout, *remainingTime) - } - desiredReplicaCount = rolloutReplicas - } - } - } - - return annotationedRSs, desiredReplicaCount -} - func GetScaleDownRevisionLimit(ro *v1alpha1.Rollout) int32 { if ro.Spec.Strategy.BlueGreen != nil { if ro.Spec.Strategy.BlueGreen.ScaleDownDelayRevisionLimit != nil { diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index a016e8ec89..a83b9071c2 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -500,7 +500,6 @@ func TestBlueGreenHandlePause(t *testing.T) { }` expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchWithoutSubs, rs2PodHash, rs2PodHash, rs2PodHash)) f.expectPatchServiceAction(activeSvc, rs2PodHash) - f.expectPatchReplicaSetAction(rs1) patchRolloutIndex := f.expectPatchRolloutActionWithPatch(r2, expectedPatch) f.run(getKey(r2, t)) @@ -580,7 +579,6 @@ func TestBlueGreenHandlePause(t *testing.T) { f.serviceLister = append(f.serviceLister, activeSvc) servicePatchIndex := f.expectPatchServiceAction(activeSvc, rs2PodHash) - patchedRSIndex := f.expectPatchReplicaSetAction(rs1) generatedConditions := generateConditionsPatch(true, conditions.ReplicaSetUpdatedReason, rs2, true, "") newSelector := metav1.FormatLabelSelector(rs2.Spec.Selector) @@ -600,7 +598,6 @@ func TestBlueGreenHandlePause(t *testing.T) { patchIndex := f.expectPatchRolloutActionWithPatch(r2, expectedPatch) f.run(getKey(r2, t)) f.verifyPatchedService(servicePatchIndex, rs2PodHash, "") - f.verifyPatchedReplicaSet(patchedRSIndex, 10) rolloutPatch := f.getPatchedRollout(patchIndex) assert.Equal(t, expectedPatch, rolloutPatch) @@ -730,13 +727,11 @@ func TestBlueGreenHandlePause(t *testing.T) { f.serviceLister = append(f.serviceLister, activeSvc, previewSvc) servicePatchIndex := f.expectPatchServiceAction(activeSvc, rs2PodHash) - patchedRSIndex := f.expectPatchReplicaSetAction(rs1) unpausePatchIndex := f.expectPatchRolloutAction(r2) patchRolloutIndex := f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) f.verifyPatchedService(servicePatchIndex, rs2PodHash, "") - f.verifyPatchedReplicaSet(patchedRSIndex, 10) unpausePatch := f.getPatchedRollout(unpausePatchIndex) unpauseConditions := generateConditionsPatch(true, conditions.RolloutResumedReason, rs2, true, "") expectedUnpausePatch := `{ @@ -791,10 +786,8 @@ func TestBlueGreenAddScaleDownDelayToPreviousActiveReplicaSet(t *testing.T) { f.serviceLister = append(f.serviceLister, s) f.expectPatchServiceAction(s, rs2PodHash) - patchedRSIndex := f.expectPatchReplicaSetAction(rs1) patchIndex := f.expectPatchRolloutAction(r2) f.run(getKey(r2, t)) - f.verifyPatchedReplicaSet(patchedRSIndex, 10) patch := f.getPatchedRollout(patchIndex) expectedPatchWithoutSubs := `{ @@ -997,7 +990,6 @@ func TestPreviewReplicaCountHandleScaleUpPreviewCheckPoint(t *testing.T) { f.kubeobjects = append(f.kubeobjects, activeSvc) f.serviceLister = append(f.serviceLister, activeSvc) - f.expectPatchReplicaSetAction(rs1) patchIndex := f.expectPatchRolloutAction(r1) f.run(getKey(r2, t)) @@ -1452,10 +1444,44 @@ func TestBlueGreenHandlePauseAutoPromoteWithConditions(t *testing.T) { assert.Nil(t, err) expectedPatch := calculatePatch(r2, fmt.Sprintf(expectedPatchWithoutSubs, rs2PodHash, string(availableCondBytes), string(pausedCondBytes), string(progressingCondBytes), rs2PodHash, rs2PodHash)) f.expectPatchServiceAction(activeSvc, rs2PodHash) - f.expectPatchReplicaSetAction(rs1) patchRolloutIndex := f.expectPatchRolloutActionWithPatch(r2, expectedPatch) f.run(getKey(r2, t)) rolloutPatch := f.getPatchedRollout(patchRolloutIndex) assert.Equal(t, expectedPatch, rolloutPatch) } + +// Verifies with blue-green, we add a scaledown delay to the old ReplicaSet after promoting desired +// ReplicaSet to stable. +// NOTE: As of v1.1, scale down delays are added to ReplicaSets on *subsequent* reconciliations +// after the desired RS has been promoted to stable +func TestBlueGreenAddScaleDownDelay(t *testing.T) { + f := newFixture(t) + defer f.Close() + + r1 := newBlueGreenRollout("foo", 1, nil, "active", "") + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 1, 1) + 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) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + activeSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} + activeSvc := newService("active", 80, activeSelector, r2) + + f.kubeobjects = append(f.kubeobjects, rs1, rs2, activeSvc) + f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2) + + rs1Patch := f.expectPatchReplicaSetAction(rs1) // set scale-down-deadline annotation + f.run(getKey(r2, t)) + + f.verifyPatchedReplicaSet(rs1Patch, 30) +} diff --git a/rollout/canary.go b/rollout/canary.go index c291835626..11813ed44b 100644 --- a/rollout/canary.go +++ b/rollout/canary.go @@ -157,6 +157,10 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForCanary(oldRSs []*appsv1.Repli sort.Sort(sort.Reverse(replicasetutil.ReplicaSetsByRevisionNumber(oldRSs))) + if canProceed, err := c.canProceedWithScaleDownAnnotation(oldRSs); !canProceed || err != nil { + return 0, err + } + annotationedRSs := int32(0) rolloutReplicas := defaults.GetReplicasOrDefault(c.rollout.Spec.Replicas) for _, targetRS := range oldRSs { @@ -169,7 +173,7 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForCanary(oldRSs []*appsv1.Repli if maxScaleDown <= 0 { break } - if *(targetRS.Spec.Replicas) == 0 { + if *targetRS.Spec.Replicas == 0 { // cannot scale down this ReplicaSet. continue } @@ -177,15 +181,18 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForCanary(oldRSs []*appsv1.Repli if c.rollout.Spec.Strategy.Canary.TrafficRouting == nil { // For basic canary, we must scale down all other ReplicaSets because existence of // those pods will cause traffic to be served by them - if *(targetRS.Spec.Replicas) > maxScaleDown { - desiredReplicaCount = *(targetRS.Spec.Replicas) - maxScaleDown + if *targetRS.Spec.Replicas > maxScaleDown { + desiredReplicaCount = *targetRS.Spec.Replicas - maxScaleDown } } else { // For traffic shaped canary, we leave the old ReplicaSets up until scaleDownDelaySeconds - annotationedRSs, desiredReplicaCount = c.ScaleDownDelayHelper(targetRS, annotationedRSs, rolloutReplicas) + annotationedRSs, desiredReplicaCount, err = c.scaleDownDelayHelper(targetRS, annotationedRSs, rolloutReplicas) + if err != nil { + return totalScaledDown, err + } } - if *(targetRS.Spec.Replicas) == desiredReplicaCount { - // at desired account + if *targetRS.Spec.Replicas == desiredReplicaCount { + // already at desired account, nothing to do continue } // Scale down. @@ -201,6 +208,50 @@ func (c *rolloutContext) scaleDownOldReplicaSetsForCanary(oldRSs []*appsv1.Repli return totalScaledDown, nil } +// canProceedWithScaleDownAnnotation returns whether or not it is safe to proceed with annotating +// old replicasets with the scale-down-deadline in the traffic-routed canary strategy. +// This method only matters with ALB canary + the target group verification feature. +// The safety guarantees we provide are that we will not scale down *anything* unless we can verify +// stable target group endpoints are registered properly. +// NOTE: this method was written in a way which avoids AWS API calls. +func (c *rolloutContext) canProceedWithScaleDownAnnotation(oldRSs []*appsv1.ReplicaSet) (bool, error) { + isALBCanary := c.rollout.Spec.Strategy.Canary != nil && c.rollout.Spec.Strategy.Canary.TrafficRouting != nil && c.rollout.Spec.Strategy.Canary.TrafficRouting.ALB != nil + if !isALBCanary { + // Only ALB + return true, nil + } + + needToVerifyTargetGroups := false + for _, targetRS := range oldRSs { + if *targetRS.Spec.Replicas > 0 && !replicasetutil.HasScaleDownDeadline(targetRS) { + // We encountered an old ReplicaSet that is not yet scaled down, and is not annotated + // We only verify target groups if there is something to scale down. + needToVerifyTargetGroups = true + break + } + } + if !needToVerifyTargetGroups { + // All ReplicaSets are either scaled down, or have a scale-down-deadline annotation. + // The presence of the scale-down-deadline on all oldRSs, implies we can proceed with + // scale down, because we only add that annotation when target groups have been verified. + // Therefore, we return true to avoid performing verification again and making unnecessary + // AWS API calls. + return true, nil + } + stableSvc, err := c.servicesLister.Services(c.rollout.Namespace).Get(c.rollout.Spec.Strategy.Canary.StableService) + if err != nil { + return false, err + } + err = c.awsVerifyTargetGroups(stableSvc) + if err != nil { + return false, err + } + + canProceed := c.areTargetsVerified() + c.log.Infof("Proceed with scaledown: %v", canProceed) + return canProceed, nil +} + func (c *rolloutContext) completedCurrentCanaryStep() bool { if c.rollout.Spec.Paused { return false @@ -218,7 +269,7 @@ func (c *rolloutContext) completedCurrentCanaryStep() bool { if !replicasetutil.AtDesiredReplicaCountsForCanary(c.rollout, c.newRS, c.stableRS, c.otherRSs) { return false } - if c.weightVerified != nil && !*c.weightVerified { + if !c.areTargetsVerified() { return false } return true diff --git a/rollout/context.go b/rollout/context.go index cf3fad0946..5ebd566409 100644 --- a/rollout/context.go +++ b/rollout/context.go @@ -42,10 +42,13 @@ type rolloutContext struct { newStatus v1alpha1.RolloutStatus pauseContext *pauseContext - // weightVerified keeps track of the verified weight. nil indicates the check was not performed. - // we only perform weight verification when we are at a setWeight step since we do not want to - // continually verify weight in case it could incur rate-limiting or other expenses. - weightVerified *bool + // targetsVerified indicates if the pods targets have been verified with underlying LoadBalancer. + // This is used in pod-aware flat networks where LoadBalancers target Pods and not Nodes. + // nil indicates the check was unnecessary or not performed. + // NOTE: we only perform target verification when we are at specific points in time + // (e.g. a setWeight step, after a blue-green active switch, after stable service switch), + // since we do not want to continually verify weight in case it could incur rate-limiting or other expenses. + targetsVerified *bool } func (c *rolloutContext) reconcile() error { diff --git a/rollout/controller_test.go b/rollout/controller_test.go index 8f9b7d346c..8fa0bd56cb 100644 --- a/rollout/controller_test.go +++ b/rollout/controller_test.go @@ -10,8 +10,6 @@ import ( "testing" "time" - "github.com/argoproj/argo-rollouts/utils/queue" - "github.com/ghodss/yaml" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -51,6 +49,7 @@ import ( "github.com/argoproj/argo-rollouts/utils/conditions" "github.com/argoproj/argo-rollouts/utils/defaults" istioutil "github.com/argoproj/argo-rollouts/utils/istio" + "github.com/argoproj/argo-rollouts/utils/queue" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" @@ -102,6 +101,8 @@ type fixture struct { enqueuedObjects map[string]int unfreezeTime func() error + // events holds all the K8s Event Reasons emitted during the run + events []string fakeTrafficRouting *mocks.TrafficRoutingReconciler } @@ -480,7 +481,17 @@ func (f *fixture) newController(resync resyncFunc) (*Controller, informers.Share k8sI := kubeinformers.NewSharedInformerFactory(f.kubeclient, resync()) // Pass in objects to to dynamicClient - dynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme()) + scheme := runtime.NewScheme() + v1alpha1.AddToScheme(scheme) + tgbGVR := schema.GroupVersionResource{ + Group: "elbv2.k8s.aws", + Version: "v1beta1", + Resource: "targetgroupbindings", + } + listMapping := map[schema.GroupVersionResource]string{ + tgbGVR: "TargetGroupBindingList", + } + dynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping, f.objects...) dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicClient, 0) istioVirtualServiceInformer := dynamicInformerFactory.ForResource(istioutil.GetIstioVirtualServiceGVR()).Informer() istioDestinationRuleInformer := dynamicInformerFactory.ForResource(istioutil.GetIstioDestinationRuleGVR()).Informer() @@ -632,6 +643,8 @@ func (f *fixture) runController(rolloutName string, startInformers bool, expectE if len(f.kubeactions) > len(k8sActions) { f.t.Errorf("%d expected actions did not happen:%+v", len(f.kubeactions)-len(k8sActions), f.kubeactions[len(k8sActions):]) } + fakeRecorder := c.recorder.(*record.FakeEventRecorder) + f.events = fakeRecorder.Events return c } @@ -821,6 +834,12 @@ func (f *fixture) expectPatchRolloutActionWithPatch(rollout *v1alpha1.Rollout, p return len } +func (f *fixture) expectGetEndpointsAction(ep *corev1.Endpoints) int { + len := len(f.kubeactions) + f.kubeactions = append(f.kubeactions, core.NewGetAction(schema.GroupVersionResource{Resource: "endpoints"}, ep.Namespace, ep.Name)) + return len +} + func (f *fixture) getCreatedReplicaSet(index int) *appsv1.ReplicaSet { action := filterInformerActions(f.kubeclient.Actions())[index] createAction, ok := action.(core.CreateAction) @@ -1072,6 +1091,11 @@ func (f *fixture) getUpdatedPod(index int) *corev1.Pod { return pod } +func (f *fixture) assertEvents(events []string) { + f.t.Helper() + assert.Equal(f.t, events, f.events) +} + func TestDontSyncRolloutsWithEmptyPodSelector(t *testing.T) { f := newFixture(t) defer f.Close() diff --git a/rollout/ephemeralmetadata_test.go b/rollout/ephemeralmetadata_test.go index a68e4f39d2..24f76d6bac 100644 --- a/rollout/ephemeralmetadata_test.go +++ b/rollout/ephemeralmetadata_test.go @@ -215,7 +215,6 @@ func TestSyncBlueGreenEphemeralMetadataSecondRevision(t *testing.T) { f.expectListPodAction(r1.Namespace) // list pods to patch ephemeral data on revision 1 ReplicaSets pods` podIdx := f.expectUpdatePodAction(&pod) // Update pod with ephemeral data rs1idx := f.expectUpdateReplicaSetAction(rs1) // update stable replicaset with stable metadata - f.expectPatchReplicaSetAction(rs1) // scale revision 1 ReplicaSet down f.expectPatchRolloutAction(r2) // Patch Rollout status f.run(getKey(r2, t)) diff --git a/rollout/replicaset.go b/rollout/replicaset.go index 32e63a589e..e2ff4ebaa3 100644 --- a/rollout/replicaset.go +++ b/rollout/replicaset.go @@ -57,7 +57,7 @@ func (c *rolloutContext) addScaleDownDelay(rs *appsv1.ReplicaSet, scaleDownDelay patch := fmt.Sprintf(addScaleDownAtAnnotationsPatch, v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, deadline) _, err := c.kubeclientset.AppsV1().ReplicaSets(rs.Namespace).Patch(ctx, rs.Name, patchtypes.JSONPatchType, []byte(patch), metav1.PatchOptions{}) if err == nil { - c.log.Infof("Set '%s' annotation on '%s' to %s (%ds)", v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, rs.Name, deadline, scaleDownDelaySeconds) + c.log.Infof("Set '%s' annotation on '%s' to %s (%s)", v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey, rs.Name, deadline, scaleDownDelaySeconds) } return err } @@ -230,3 +230,39 @@ func (c *rolloutContext) cleanupUnhealthyReplicas(oldRSs []*appsv1.ReplicaSet) ( } return oldRSs, totalScaledDown, nil } + +func (c *rolloutContext) scaleDownDelayHelper(rs *appsv1.ReplicaSet, annotationedRSs int32, rolloutReplicas int32) (int32, int32, error) { + desiredReplicaCount := int32(0) + scaleDownRevisionLimit := GetScaleDownRevisionLimit(c.rollout) + if !replicasetutil.HasScaleDownDeadline(rs) && *rs.Spec.Replicas > 0 { + // This ReplicaSet is scaled up but does not have a scale down deadline. Add one. + if annotationedRSs < scaleDownRevisionLimit { + annotationedRSs++ + desiredReplicaCount = *rs.Spec.Replicas + scaleDownDelaySeconds := defaults.GetScaleDownDelaySecondsOrDefault(c.rollout) + err := c.addScaleDownDelay(rs, scaleDownDelaySeconds) + if err != nil { + return annotationedRSs, desiredReplicaCount, err + } + c.enqueueRolloutAfter(c.rollout, scaleDownDelaySeconds) + } + } else if replicasetutil.HasScaleDownDeadline(rs) { + annotationedRSs++ + if annotationedRSs > scaleDownRevisionLimit { + c.log.Infof("At ScaleDownDelayRevisionLimit (%d) and scaling down the rest", scaleDownRevisionLimit) + } else { + remainingTime, err := replicasetutil.GetTimeRemainingBeforeScaleDownDeadline(rs) + if err != nil { + c.log.Warnf("%v", err) + } else if remainingTime != nil { + c.log.Infof("RS '%s' has not reached the scaleDownTime", rs.Name) + if *remainingTime < c.resyncPeriod { + c.enqueueRolloutAfter(c.rollout, *remainingTime) + } + desiredReplicaCount = rolloutReplicas + } + } + } + + return annotationedRSs, desiredReplicaCount, nil +} diff --git a/rollout/replicaset_test.go b/rollout/replicaset_test.go index 5bf7ac86ed..481454d843 100644 --- a/rollout/replicaset_test.go +++ b/rollout/replicaset_test.go @@ -10,6 +10,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sfake "k8s.io/client-go/kubernetes/fake" core "k8s.io/client-go/testing" + "k8s.io/utils/pointer" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/pkg/client/clientset/versioned/fake" @@ -307,6 +308,7 @@ func TestReconcileOldReplicaSet(t *testing.T) { oldRS.Annotations = map[string]string{annotations.DesiredReplicasAnnotation: strconv.Itoa(test.oldReplicas)} oldRS.Status.AvailableReplicas = int32(test.readyPodsFromOldRS) rollout := newBlueGreenRollout("foo", test.rolloutReplicas, nil, "", "") + rollout.Spec.Strategy.BlueGreen.ScaleDownDelayRevisionLimit = pointer.Int32Ptr(0) rollout.Spec.Selector = &metav1.LabelSelector{MatchLabels: newSelector} f := newFixture(t) defer f.Close() diff --git a/rollout/service.go b/rollout/service.go index dde65f9a9c..8e8d9ffcb7 100644 --- a/rollout/service.go +++ b/rollout/service.go @@ -3,14 +3,20 @@ package rollout import ( "context" "fmt" + "time" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" patchtypes "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/utils/annotations" + "github.com/argoproj/argo-rollouts/utils/aws" + "github.com/argoproj/argo-rollouts/utils/conditions" + "github.com/argoproj/argo-rollouts/utils/defaults" + logutil "github.com/argoproj/argo-rollouts/utils/log" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" serviceutil "github.com/argoproj/argo-rollouts/utils/service" @@ -105,6 +111,110 @@ func (c *rolloutContext) reconcileActiveService(activeSvc *corev1.Service) error return nil } +// areTargetsVerified is a convenience to determine if the pod targets have been verified with +// underlying load balancer. If check was not performed or unnecessary, returns true. +func (c *rolloutContext) areTargetsVerified() bool { + return c.targetsVerified == nil || *c.targetsVerified +} + +// awsVerifyTargetGroups examines a Service and verifies that the underlying AWS TargetGroup has all +// of the Service's Endpoint IPs and ports registered. Only valid for services which are reachable +// by an ALB Ingress, which can be determined if there exists a TargetGroupBinding object in the +// namespace that references the given service +func (c *rolloutContext) awsVerifyTargetGroups(svc *corev1.Service) error { + if !c.shouldVerifyTargetGroup(svc) { + return nil + } + logCtx := c.log.WithField(logutil.ServiceKey, svc.Name) + logCtx.Infof("Verifying target group") + + ctx := context.TODO() + // find all TargetGroupBindings in the namespace which reference the service name + port + tgBindings, err := aws.GetTargetGroupBindingsByService(ctx, c.dynamicclientset, *svc) + if err != nil { + return err + } + if len(tgBindings) == 0 { + // no TargetGroupBinding for the service found (e.g. it is in-cluster blue-green). nothing to verify + return nil + } + + c.targetsVerified = pointer.BoolPtr(false) + + // get endpoints of service + endpoints, err := c.kubeclientset.CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + awsClnt, err := aws.NewClient() + if err != nil { + return err + } + + for _, tgb := range tgBindings { + verifyRes, err := aws.VerifyTargetGroupBinding(ctx, c.log, awsClnt, tgb, endpoints, svc) + if err != nil { + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, svc.Name, tgb.Spec.TargetGroupARN, err) + return err + } + if verifyRes == nil { + // verification not applicable + continue + } + if !verifyRes.Verified { + c.recorder.Warnf(c.rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedRegistrationMessage, svc.Name, tgb.Spec.TargetGroupARN, verifyRes.EndpointsRegistered, verifyRes.EndpointsTotal) + c.enqueueRolloutAfter(c.rollout, 10*time.Second) + return nil + } + c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedRegistrationMessage, svc.Name, tgb.Spec.TargetGroupARN, verifyRes.EndpointsRegistered) + } + c.targetsVerified = pointer.BoolPtr(true) + return nil +} + +// shouldVerifyTargetGroup returns whether or not we should verify the target group +func (c *rolloutContext) shouldVerifyTargetGroup(svc *corev1.Service) bool { + if !defaults.VerifyTargetGroup() { + // feature is disabled + return false + } + desiredPodHash := c.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + if c.rollout.Spec.Strategy.BlueGreen != nil { + if c.rollout.Status.StableRS == desiredPodHash { + // for blue-green, we only verify targets right after switching active service. So if + // we are fully promoted, then there is no need to verify targets. + // NOTE: this is the opposite of canary, where we only verify targets if stable == desired + return false + } + svcPodHash := svc.Spec.Selector[v1alpha1.DefaultRolloutUniqueLabelKey] + if svcPodHash != desiredPodHash { + // we have not yet switched service selector + return false + } + if c.rollout.Status.BlueGreen.PostPromotionAnalysisRunStatus != nil { + // we already started post-promotion analysis, so verification already occurred + return false + } + return true + } else if c.rollout.Spec.Strategy.Canary != nil { + if c.rollout.Spec.Strategy.Canary.TrafficRouting == nil || c.rollout.Spec.Strategy.Canary.TrafficRouting.ALB == nil { + // not ALB canary, so no need to verify targets + return false + } + if c.rollout.Status.StableRS != desiredPodHash { + // for canary, we only verify targets right after switching stable service, which happens + // after the update. So if stable != desired, we are still in the middle of an update + // and there is no need to verify targets. + // NOTE: this is the opposite of blue-green, where we only verify targets if stable != active + return false + } + return true + } + // should not get here + return false +} + func (c *rolloutContext) getPreviewAndActiveServices() (*corev1.Service, *corev1.Service, error) { var previewSvc *corev1.Service var activeSvc *corev1.Service diff --git a/rollout/service_test.go b/rollout/service_test.go index b4a4d5f059..897382e135 100644 --- a/rollout/service_test.go +++ b/rollout/service_test.go @@ -2,17 +2,29 @@ package rollout import ( "fmt" + "strconv" "strings" "testing" + "time" + elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/aws" + "github.com/argoproj/argo-rollouts/utils/aws/mocks" "github.com/argoproj/argo-rollouts/utils/conditions" + "github.com/argoproj/argo-rollouts/utils/defaults" + unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" ) func newService(name string, port int, selector map[string]string, ro *v1alpha1.Rollout) *corev1.Service { @@ -162,3 +174,559 @@ func TestPreviewServiceNotFound(t *testing.T) { assert.Equal(t, calculatePatch(r, fmt.Sprintf(expectedPatch, pausedCondition, conditions.InvalidSpecReason, strings.ReplaceAll(errmsg, "\"", "\\\""))), patch) } + +func newEndpoints(name string, ips ...string) *corev1.Endpoints { + ep := corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{}, + }, + }, + } + for _, ip := range ips { + address := corev1.EndpointAddress{ + IP: ip, + } + ep.Subsets[0].Addresses = append(ep.Subsets[0].Addresses, address) + } + return &ep +} + +func newTargetGroupBinding(name string) *unstructured.Unstructured { + return unstructuredutil.StrToUnstructuredUnsafe(fmt.Sprintf(` +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: %s + namespace: default +spec: + serviceRef: + name: %s + port: 80 + targetGroupARN: arn::1234 + targetType: ip +`, name, name)) +} + +func newIngress(name string, canary, stable *corev1.Service) *extensionsv1beta1.Ingress { + ingress := extensionsv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: metav1.NamespaceDefault, + }, + Spec: extensionsv1beta1.IngressSpec{ + Rules: []extensionsv1beta1.IngressRule{ + { + Host: "fakehost.example.com", + IngressRuleValue: extensionsv1beta1.IngressRuleValue{ + HTTP: &extensionsv1beta1.HTTPIngressRuleValue{ + Paths: []extensionsv1beta1.HTTPIngressPath{ + { + Path: "/foo", + Backend: extensionsv1beta1.IngressBackend{ + ServiceName: "root", + ServicePort: intstr.FromString("use-annotations"), + }, + }, + }, + }, + }, + }, + }, + }, + } + return &ingress +} + +// TestBlueGreenAWSVerifyTargetGroupsNotYetReady verifies we don't proceed with setting stable with +// the blue-green strategy until target group verification is successful +func TestBlueGreenAWSVerifyTargetGroupsNotYetReady(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + // Allow us to fake out the AWS API + fakeELB := mocks.ELBv2APIClient{} + aws.NewClient = aws.FakeNewClientFunc(&fakeELB) + defer func() { + aws.NewClient = aws.DefaultNewClientFunc + }() + + f := newFixture(t) + defer f.Close() + + tgb := newTargetGroupBinding("active") + ep := newEndpoints("active", "1.2.3.4", "5.6.7.8", "2.4.6.8") + thOut := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("1.2.3.4"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("5.6.7.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("2.4.6.8"), // irrelevant + Port: pointer.Int32Ptr(81), // wrong port + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("9.8.7.6"), // irrelevant ip + Port: pointer.Int32Ptr(80), + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) + + r1 := newBlueGreenRollout("foo", 3, nil, "active", "") + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 3, 3) + rs2 := newReplicaSetWithStatus(r2, 3, 3) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + 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.Status.Message = "" + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2, tgb) + f.kubeobjects = append(f.kubeobjects, rs1, rs2, svc, ep) + f.serviceLister = append(f.serviceLister, svc) + + f.expectGetEndpointsAction(ep) + patchIndex := f.expectPatchRolloutAction(r2) // update status message + f.run(getKey(r2, t)) + + patch := f.getPatchedRollout(patchIndex) + expectedPatch := `{"status":{"message":"waiting for post-promotion verification to complete"}}` + assert.Equal(t, expectedPatch, patch) + f.assertEvents([]string{ + conditions.TargetGroupUnverifiedReason, + }) +} + +// TestBlueGreenAWSVerifyTargetGroupsReady verifies we proceed with setting stable with +// the blue-green strategy when target group verification is successful +func TestBlueGreenAWSVerifyTargetGroupsReady(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + // Allow us to fake out the AWS API + fakeELB := mocks.ELBv2APIClient{} + aws.NewClient = aws.FakeNewClientFunc(&fakeELB) + defer func() { + aws.NewClient = aws.DefaultNewClientFunc + }() + + f := newFixture(t) + defer f.Close() + + tgb := newTargetGroupBinding("active") + ep := newEndpoints("active", "1.2.3.4", "5.6.7.8", "2.4.6.8") + thOut := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("1.2.3.4"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("5.6.7.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("2.4.6.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("9.8.7.6"), // irrelevant ip + Port: pointer.Int32Ptr(80), + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) + + r1 := newBlueGreenRollout("foo", 3, nil, "active", "") + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 3, 3) + rs2 := newReplicaSetWithStatus(r2, 3, 3) + + rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + 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.Status.Message = "waiting for post-promotion verification to complete" + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) + completedCondition, _ := newCompletedCondition(true) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2, tgb) + f.kubeobjects = append(f.kubeobjects, rs1, rs2, svc, ep) + f.serviceLister = append(f.serviceLister, svc) + + f.expectGetEndpointsAction(ep) + patchIndex := f.expectPatchRolloutAction(r2) // update status message + f.run(getKey(r2, t)) + + patch := f.getPatchedRollout(patchIndex) + expectedPatch := fmt.Sprintf(`{"status":{"message":null,"phase":"Healthy","stableRS":"%s"}}`, rs2PodHash) + assert.Equal(t, expectedPatch, patch) + f.assertEvents([]string{ + conditions.TargetGroupVerifiedReason, + conditions.RolloutCompletedReason, + }) +} + +// TestCanaryAWSVerifyTargetGroupsNotYetReady verifies we don't proceed with scale down of old +// ReplicaSets in the canary strategy until target group verification is successful +func TestCanaryAWSVerifyTargetGroupsNotYetReady(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + // Allow us to fake out the AWS API + fakeELB := mocks.ELBv2APIClient{} + aws.NewClient = aws.FakeNewClientFunc(&fakeELB) + defer func() { + aws.NewClient = aws.DefaultNewClientFunc + }() + + f := newFixture(t) + defer f.Close() + + tgb := newTargetGroupBinding("stable") + ep := newEndpoints("stable", "1.2.3.4", "5.6.7.8", "2.4.6.8") + thOut := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("1.2.3.4"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("5.6.7.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("2.4.6.8"), // irrelevant + Port: pointer.Int32Ptr(81), // wrong port + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("9.8.7.6"), // irrelevant ip + Port: pointer.Int32Ptr(80), + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) + + r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "ingress", + RootService: "root", + }, + } + r1.Spec.Strategy.Canary.CanaryService = "canary" + r1.Spec.Strategy.Canary.StableService = "stable" + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 3, 3) + rs2 := newReplicaSetWithStatus(r2, 3, 3) + + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + rootSvc := newService("root", 80, map[string]string{"app": "foo"}, nil) + stableSvc := newService("canary", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + canarySvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + ing := newIngress("ingress", canarySvc, stableSvc) + + r2 = updateCanaryRolloutStatus(r2, rs2PodHash, 6, 3, 6, false) + r2.Status.Message = "" + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) + r2.Status.StableRS = rs2PodHash + availableCondition, _ := newAvailableCondition(true) + conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2, tgb) + f.kubeobjects = append(f.kubeobjects, rs1, rs2, ing, rootSvc, canarySvc, stableSvc, ep) + f.serviceLister = append(f.serviceLister, rootSvc, canarySvc, stableSvc) + + f.expectGetEndpointsAction(ep) + f.run(getKey(r2, t)) + f.assertEvents([]string{ + conditions.TargetGroupUnverifiedReason, + }) +} + +// TestCanaryAWSVerifyTargetGroupsReady verifies we proceed with scale down of old +// ReplicaSets in the canary strategy after target group verification is successful +func TestCanaryAWSVerifyTargetGroupsReady(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + // Allow us to fake out the AWS API + fakeELB := mocks.ELBv2APIClient{} + aws.NewClient = aws.FakeNewClientFunc(&fakeELB) + defer func() { + aws.NewClient = aws.DefaultNewClientFunc + }() + + f := newFixture(t) + defer f.Close() + + tgb := newTargetGroupBinding("stable") + ep := newEndpoints("stable", "1.2.3.4", "5.6.7.8", "2.4.6.8") + thOut := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("1.2.3.4"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("5.6.7.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("2.4.6.8"), // irrelevant + Port: pointer.Int32Ptr(80), // wrong port + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("9.8.7.6"), // irrelevant ip + Port: pointer.Int32Ptr(80), + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) + + r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "ingress", + RootService: "root", + }, + } + r1.Spec.Strategy.Canary.CanaryService = "canary" + r1.Spec.Strategy.Canary.StableService = "stable" + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 3, 3) + rs2 := newReplicaSetWithStatus(r2, 3, 3) + + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + rootSvc := newService("root", 80, map[string]string{"app": "foo"}, nil) + stableSvc := newService("canary", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + canarySvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + ing := newIngress("ingress", canarySvc, stableSvc) + + r2 = updateCanaryRolloutStatus(r2, rs2PodHash, 6, 3, 6, false) + r2.Status.Message = "" + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) + r2.Status.StableRS = rs2PodHash + availableCondition, _ := newAvailableCondition(true) + conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2, tgb) + f.kubeobjects = append(f.kubeobjects, rs1, rs2, ing, rootSvc, canarySvc, stableSvc, ep) + f.serviceLister = append(f.serviceLister, rootSvc, canarySvc, stableSvc) + + f.expectGetEndpointsAction(ep) + scaleDownRSIndex := f.expectPatchReplicaSetAction(rs1) + f.run(getKey(r2, t)) + f.verifyPatchedReplicaSet(scaleDownRSIndex, 30) + f.assertEvents([]string{ + conditions.TargetGroupVerifiedReason, + }) +} + +// TestCanaryAWSVerifyTargetGroupsSkip verifies we skip unnecessary verification if scaledown +// annotation does not need to happen +func TestCanaryAWSVerifyTargetGroupsSkip(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + f := newFixture(t) + defer f.Close() + + r1 := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + r1.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "ingress", + RootService: "root", + }, + } + r1.Spec.Strategy.Canary.CanaryService = "canary" + r1.Spec.Strategy.Canary.StableService = "stable" + r2 := bumpVersion(r1) + + rs1 := newReplicaSetWithStatus(r1, 3, 3) + // set an annotation on old RS to cause verification to be skipped + rs1.Annotations[v1alpha1.DefaultReplicaSetScaleDownDeadlineAnnotationKey] = metav1.Now().Add(600 * time.Second).UTC().Format(time.RFC3339) + rs2 := newReplicaSetWithStatus(r2, 3, 3) + + rs2PodHash := rs2.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + + rootSvc := newService("root", 80, map[string]string{"app": "foo"}, nil) + stableSvc := newService("canary", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + canarySvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash}, r2) + ing := newIngress("ingress", canarySvc, stableSvc) + + r2 = updateCanaryRolloutStatus(r2, rs2PodHash, 6, 3, 6, false) + r2.Status.Message = "" + r2.Status.ObservedGeneration = strconv.Itoa(int(r2.Generation)) + r2.Status.StableRS = rs2PodHash + availableCondition, _ := newAvailableCondition(true) + conditions.SetRolloutCondition(&r2.Status, availableCondition) + completedCondition, _ := newCompletedCondition(false) + conditions.SetRolloutCondition(&r2.Status, completedCondition) + progressingCondition, _ := newProgressingCondition(conditions.NewRSAvailableReason, rs2, "") + conditions.SetRolloutCondition(&r2.Status, progressingCondition) + + f.rolloutLister = append(f.rolloutLister, r2) + f.objects = append(f.objects, r2) + f.kubeobjects = append(f.kubeobjects, rs1, rs2, ing, rootSvc, canarySvc, stableSvc) + f.serviceLister = append(f.serviceLister, rootSvc, canarySvc, stableSvc) + + f.run(getKey(r2, t)) // there should be no api calls + f.assertEvents(nil) +} + +// TestShouldVerifyTargetGroups returns whether or not we should verify the target group +func TestShouldVerifyTargetGroups(t *testing.T) { + defaults.SetVerifyTargetGroup(true) + defer defaults.SetVerifyTargetGroup(false) + + f := newFixture(t) + defer f.Close() + ctrl, _, _ := f.newController(noResyncPeriodFunc) + + t.Run("CanaryNotUsingTrafficRouting", func(t *testing.T) { + ro := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + stableSvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + assert.NoError(t, err) + assert.False(t, roCtx.shouldVerifyTargetGroup(stableSvc)) + }) + t.Run("CanaryNotFullyPromoted", func(t *testing.T) { + ro := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + ro.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "ingress", + }, + } + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + stableSvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + ro.Status.StableRS = "somethingelse" + assert.NoError(t, err) + assert.False(t, roCtx.shouldVerifyTargetGroup(stableSvc)) + }) + t.Run("CanaryFullyPromoted", func(t *testing.T) { + ro := newCanaryRollout("foo", 3, nil, nil, nil, intstr.FromString("25%"), intstr.FromString("25%")) + ro.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{ + Ingress: "ingress", + }, + } + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + stableSvc := newService("stable", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + ro.Status.StableRS = roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + assert.NoError(t, err) + assert.True(t, roCtx.shouldVerifyTargetGroup(stableSvc)) + }) + t.Run("BlueGreenFullyPromoted", func(t *testing.T) { + ro := newBlueGreenRollout("foo", 3, nil, "active-svc", "") + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + activeSvc := newService("active-svc", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + ro.Status.StableRS = roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + assert.NoError(t, err) + assert.False(t, roCtx.shouldVerifyTargetGroup(activeSvc)) + }) + t.Run("BlueGreenBeforePromotion", func(t *testing.T) { + ro := newBlueGreenRollout("foo", 3, nil, "active-svc", "") + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + activeSvc := newService("active-svc", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: "oldrshash"}, ro) + ro.Status.StableRS = "oldrshash" + assert.NoError(t, err) + assert.False(t, roCtx.shouldVerifyTargetGroup(activeSvc)) + }) + t.Run("BlueGreenAfterPromotionAfterPromotionAnalysisStarted", func(t *testing.T) { + ro := newBlueGreenRollout("foo", 3, nil, "active-svc", "") + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + activeSvc := newService("active-svc", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + ro.Status.StableRS = "oldrshash" + ro.Status.BlueGreen.PostPromotionAnalysisRunStatus = &v1alpha1.RolloutAnalysisRunStatus{} + assert.NoError(t, err) + assert.False(t, roCtx.shouldVerifyTargetGroup(activeSvc)) + }) + t.Run("BlueGreenAfterPromotion", func(t *testing.T) { + ro := newBlueGreenRollout("foo", 3, nil, "active-svc", "") + roCtx, err := ctrl.newRolloutContext(ro) + roCtx.newRS = newReplicaSetWithStatus(ro, 3, 3) + activeSvc := newService("active-svc", 80, map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: roCtx.newRS.Labels[v1alpha1.DefaultRolloutUniqueLabelKey]}, ro) + ro.Status.StableRS = "oldrshash" + assert.NoError(t, err) + assert.True(t, roCtx.shouldVerifyTargetGroup(activeSvc)) + }) +} diff --git a/rollout/sync.go b/rollout/sync.go index 978e19b8b1..e54f3375e6 100644 --- a/rollout/sync.go +++ b/rollout/sync.go @@ -887,6 +887,10 @@ func (c *rolloutContext) shouldFullPromote(newStatus v1alpha1.RolloutStatus) str // active selector still pointing to previous RS, don't update stable yet return "" } + if !c.areTargetsVerified() { + // active selector is pointing to desired RS, but we have not verify the target group yet + return "" + } if c.rollout.Status.PromoteFull { return "Full promotion requested" } @@ -932,15 +936,6 @@ func (c *rolloutContext) promoteStable(newStatus *v1alpha1.RolloutStatus, reason revision, _ := replicasetutil.Revision(c.rollout) c.recorder.Eventf(c.rollout, record.EventOptions{EventReason: conditions.RolloutCompletedReason}, conditions.RolloutCompletedMessage, revision, newStatus.CurrentPodHash, reason) - // Now that we've marked the desired RS as stable, start the scale-down countdown on the previous stable RS - previousStableRS, _ := replicasetutil.GetReplicaSetByTemplateHash(c.olderRSs, previousStableHash) - if replicasetutil.GetReplicaCountForReplicaSets([]*appsv1.ReplicaSet{previousStableRS}) > 0 { - scaleDownDelaySeconds := defaults.GetScaleDownDelaySecondsOrDefault(c.rollout) - err := c.addScaleDownDelay(previousStableRS, scaleDownDelaySeconds) - if err != nil { - return err - } - } } return nil } diff --git a/rollout/sync_test.go b/rollout/sync_test.go index ba3eb6f2c5..2687d55fce 100644 --- a/rollout/sync_test.go +++ b/rollout/sync_test.go @@ -365,7 +365,6 @@ func TestBlueGreenPromoteFull(t *testing.T) { f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) f.expectPatchServiceAction(activeSvc, rs2PodHash) // update active to rs2 - f.expectPatchReplicaSetAction(rs1) // set scaledown delay on rs1 patchRolloutIdx := f.expectPatchRolloutAction(r2) // update rollout status f.run(getKey(r2, t)) diff --git a/rollout/trafficrouting.go b/rollout/trafficrouting.go index a6444b1ce5..4cffd358e5 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -13,6 +13,7 @@ import ( "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/record" replicasetutil "github.com/argoproj/argo-rollouts/utils/replicaset" + rolloututil "github.com/argoproj/argo-rollouts/utils/rollout" ) // NewTrafficRoutingReconciler identifies return the TrafficRouting Plugin that the rollout wants to modify @@ -86,7 +87,7 @@ func (c *rolloutContext) reconcileTrafficRouting() error { currentStep, index := replicasetutil.GetCurrentCanaryStep(c.rollout) desiredWeight := int32(0) weightDestinations := make([]trafficrouting.WeightDestination, 0) - if c.rollout.Status.StableRS == c.rollout.Status.CurrentPodHash { + if rolloututil.IsFullyPromoted(c.rollout) { // when we are fully promoted. desired canary weight should be 0 } else if c.pauseContext.IsAborted() { // when promote aborted. desired canary weight should be 0 @@ -142,10 +143,14 @@ func (c *rolloutContext) reconcileTrafficRouting() error { return err } - // If we are 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 - if currentStep != nil && currentStep.SetWeight != nil { + // 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 != "" && + c.rollout.Status.CurrentPodHash != c.rollout.Status.StableRS && + currentStep != nil && currentStep.SetWeight != nil + + if shouldVerifyWeight { weightVerified, err := reconciler.VerifyWeight(desiredWeight) if err != nil { return err @@ -156,7 +161,7 @@ func (c *rolloutContext) reconcileTrafficRouting() error { } else { c.log.Infof("Desired weight (stepIdx: %d) %d verified", *index, desiredWeight) } - c.weightVerified = &weightVerified + c.targetsVerified = &weightVerified } return nil diff --git a/rollout/trafficrouting/alb/alb.go b/rollout/trafficrouting/alb/alb.go index b0ea0499e2..8f660c3fe9 100644 --- a/rollout/trafficrouting/alb/alb.go +++ b/rollout/trafficrouting/alb/alb.go @@ -17,6 +17,8 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/rollout/trafficrouting" "github.com/argoproj/argo-rollouts/utils/aws" + "github.com/argoproj/argo-rollouts/utils/conditions" + "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/argoproj/argo-rollouts/utils/diff" ingressutil "github.com/argoproj/argo-rollouts/utils/ingress" jsonutil "github.com/argoproj/argo-rollouts/utils/json" @@ -46,15 +48,6 @@ type Reconciler struct { aws aws.Client } -var ( - defaultVerifyWeight = false -) - -// SetDefaultVerifyWeight sets the default setWeight verification when instantiating the reconciler -func SetDefaultVerifyWeight(b bool) { - defaultVerifyWeight = b -} - // NewReconciler returns a reconciler struct that brings the ALB Ingress into the desired state func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { awsClient, err := aws.NewClient() @@ -120,7 +113,7 @@ func (r *Reconciler) shouldVerifyWeight() bool { if r.cfg.VerifyWeight != nil { return *r.cfg.VerifyWeight } - return defaultVerifyWeight + return defaults.VerifyTargetGroup() } func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { @@ -135,7 +128,7 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { return false, err } canaryService := rollout.Spec.Strategy.Canary.CanaryService - resourceID := aws.BuildV2TargetGroupID(rollout.Namespace, ingress.Name, canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + resourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.Name, canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) if len(ingress.Status.LoadBalancer.Ingress) == 0 { r.log.Infof("LoadBalancer not yet allocated") } @@ -145,6 +138,7 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { } lb, err := r.aws.FindLoadBalancerByDNSName(ctx, lbIngress.Hostname) if err != nil { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) return false, err } if lb == nil || lb.LoadBalancerArn == nil { @@ -153,6 +147,7 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { } lbTargetGroups, err := r.aws.GetTargetGroupMetadata(ctx, *lb.LoadBalancerArn) if err != nil { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifyErrorReason}, conditions.TargetGroupVerifyErrorMessage, canaryService, "unknown", err.Error()) return false, err } logCtx := r.log.WithField("lb", *lb.LoadBalancerArn) @@ -161,7 +156,13 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { if tg.Weight != nil { logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) logCtx.Infof("canary weight of %s (desired: %d, current: %d)", resourceID, desiredWeight, *tg.Weight) - return *tg.Weight == desiredWeight, nil + verified := *tg.Weight == desiredWeight + if verified { + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight) + } else { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight, *tg.Weight) + } + return verified, nil } } } diff --git a/rollout/trafficrouting/alb/alb_test.go b/rollout/trafficrouting/alb/alb_test.go index 949a73df38..c8146e6b45 100644 --- a/rollout/trafficrouting/alb/alb_test.go +++ b/rollout/trafficrouting/alb/alb_test.go @@ -271,8 +271,9 @@ func TestErrorPatching(t *testing.T) { } type fakeAWSClient struct { - targetGroups []aws.TargetGroupMeta - loadBalancer *elbv2types.LoadBalancer + targetGroups []aws.TargetGroupMeta + loadBalancer *elbv2types.LoadBalancer + targetHealthDescriptions []elbv2types.TargetHealthDescription } func (f *fakeAWSClient) GetTargetGroupMetadata(ctx context.Context, loadBalancerARN string) ([]aws.TargetGroupMeta, error) { @@ -283,6 +284,10 @@ func (f *fakeAWSClient) FindLoadBalancerByDNSName(ctx context.Context, dnsName s return f.loadBalancer, nil } +func (f *fakeAWSClient) GetTargetGroupHealth(ctx context.Context, targetGroupARN string) ([]elbv2types.TargetHealthDescription, error) { + return f.targetHealthDescriptions, nil +} + func TestVerifyWeight(t *testing.T) { newFakeReconciler := func() (*Reconciler, *fakeAWSClient) { ro := fakeRollout("stable-svc", "canary-svc", "ingress", 443) diff --git a/rollout/trafficrouting/ambassador/ambassador.go b/rollout/trafficrouting/ambassador/ambassador.go index 859fac0b6f..e520a18c57 100644 --- a/rollout/trafficrouting/ambassador/ambassador.go +++ b/rollout/trafficrouting/ambassador/ambassador.go @@ -36,21 +36,12 @@ const ( ) var ( - ambassadorAPIVersion = defaults.DefaultAmbassadorVersion - apiGroupToResource = map[string]string{ + apiGroupToResource = map[string]string{ "getambassador.io": "mappings", "x.getambassador.io": "ambassadormappings", } ) -func SetAPIVersion(apiVersion string) { - ambassadorAPIVersion = apiVersion -} - -func GetAPIVersion() string { - return ambassadorAPIVersion -} - // Reconciler implements a TrafficRoutingReconciler for Ambassador. type Reconciler struct { Rollout *v1alpha1.Rollout @@ -300,7 +291,7 @@ func buildCanaryMappingName(name string) string { // ambassadorAPIVersion variable that is set with a default value. The default value can be // changed by invoking the SetAPIVersion function. func GetMappingGVR() schema.GroupVersionResource { - return toMappingGVR(ambassadorAPIVersion) + return toMappingGVR(defaults.GetAmbassadorAPIVersion()) } func toMappingGVR(apiVersion string) schema.GroupVersionResource { diff --git a/rollout/trafficrouting/ambassador/ambassador_test.go b/rollout/trafficrouting/ambassador/ambassador_test.go index 4777d109a5..39bebf4d4a 100644 --- a/rollout/trafficrouting/ambassador/ambassador_test.go +++ b/rollout/trafficrouting/ambassador/ambassador_test.go @@ -17,6 +17,7 @@ import ( "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" "github.com/argoproj/argo-rollouts/rollout/trafficrouting/ambassador" + "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/argoproj/argo-rollouts/utils/record" ) @@ -544,7 +545,7 @@ func TestGetMappingGVR(t *testing.T) { }) t.Run("will get gvr successfully", func(t *testing.T) { // given - ambassador.SetAPIVersion("v1") + defaults.SetAmbassadorAPIVersion("v1") // when gvr := ambassador.GetMappingGVR() @@ -557,7 +558,7 @@ func TestGetMappingGVR(t *testing.T) { t.Run("will get valid gvr even if apiVersion has the wrong domain", func(t *testing.T) { // given apiVersion := "invalid.com/v1alpha1" - ambassador.SetAPIVersion(apiVersion) + defaults.SetAmbassadorAPIVersion(apiVersion) // when gvr := ambassador.GetMappingGVR() @@ -566,12 +567,12 @@ func TestGetMappingGVR(t *testing.T) { assert.Equal(t, "invalid.com", gvr.Group) assert.Equal(t, "v1alpha1", gvr.Version) assert.Equal(t, "mappings", gvr.Resource) - assert.Equal(t, apiVersion, ambassador.GetAPIVersion()) + assert.Equal(t, apiVersion, defaults.GetAmbassadorAPIVersion()) }) t.Run("will get correct gvr for x.getambassador.io api group", func(t *testing.T) { // given apiVersion := "x.getambassador.io/v3alpha1" - ambassador.SetAPIVersion(apiVersion) + defaults.SetAmbassadorAPIVersion(apiVersion) // when gvr := ambassador.GetMappingGVR() @@ -580,7 +581,7 @@ func TestGetMappingGVR(t *testing.T) { assert.Equal(t, "x.getambassador.io", gvr.Group) assert.Equal(t, "v3alpha1", gvr.Version) assert.Equal(t, "ambassadormappings", gvr.Resource) - assert.Equal(t, apiVersion, ambassador.GetAPIVersion()) + assert.Equal(t, apiVersion, defaults.GetAmbassadorAPIVersion()) }) } diff --git a/rollout/trafficrouting/smi/smi.go b/rollout/trafficrouting/smi/smi.go index 5ce358eb6c..6cf372546f 100644 --- a/rollout/trafficrouting/smi/smi.go +++ b/rollout/trafficrouting/smi/smi.go @@ -52,12 +52,6 @@ type VersionedTrafficSplits struct { ts3 *smiv1alpha3.TrafficSplit } -var smiAPIVersion = defaults.DefaultSMITrafficSplitVersion - -func SetSMIAPIVersion(apiVersion string) { - smiAPIVersion = apiVersion -} - // NewReconciler returns a reconciler struct that brings the SMI into the desired state func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { r := &Reconciler{ @@ -65,7 +59,7 @@ func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { log: logutil.WithRollout(cfg.Rollout), } ctx := context.TODO() - switch smiAPIVersion { + switch defaults.GetSMIAPIVersion() { case "v1alpha1": r.getTrafficSplit = func(trafficSplitName string) (VersionedTrafficSplits, error) { ts1, err := r.cfg.Client.SplitV1alpha1().TrafficSplits(r.cfg.Rollout.Namespace).Get(ctx, trafficSplitName, metav1.GetOptions{}) @@ -175,7 +169,7 @@ func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { return metav1.IsControlledBy(ts.ts3, r.cfg.Rollout) } default: - err := fmt.Errorf("Unsupported TrafficSplit API version `%s`", smiAPIVersion) + err := fmt.Errorf("Unsupported TrafficSplit API version `%s`", defaults.GetSMIAPIVersion()) return nil, err } return r, nil @@ -240,7 +234,7 @@ func (r *Reconciler) generateTrafficSplits(trafficSplitName string, desiredWeigh objectMeta := objectMeta(trafficSplitName, r.cfg.Rollout, r.cfg.ControllerKind) - switch smiAPIVersion { + switch defaults.GetSMIAPIVersion() { case "v1alpha1": trafficSplits.ts1 = trafficSplitV1Alpha1(r.cfg.Rollout, objectMeta, rootSvc, desiredWeight, additionalDestinations...) case "v1alpha2": diff --git a/rollout/trafficrouting/smi/smi_test.go b/rollout/trafficrouting/smi/smi_test.go index ba40903dcb..038c4a7e77 100644 --- a/rollout/trafficrouting/smi/smi_test.go +++ b/rollout/trafficrouting/smi/smi_test.go @@ -65,8 +65,8 @@ func TestType(t *testing.T) { func TestUnsupportedTrafficSplitApiVersionError(t *testing.T) { ro := fakeRollout("stable-service", "canary-service", "root-service", "traffic-split-name") client := fake.NewSimpleClientset() - SetSMIAPIVersion("does-not-exist") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("does-not-exist") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) _, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -114,8 +114,8 @@ func TestReconcileCreateNewTrafficSplit(t *testing.T) { t.Run("v1alpha2", func(t *testing.T) { ro := fakeRollout("stable-service", "canary-service", "root-service", "traffic-split-name") client := fake.NewSimpleClientset() - SetSMIAPIVersion("v1alpha2") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha2") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -145,8 +145,8 @@ func TestReconcileCreateNewTrafficSplit(t *testing.T) { t.Run("v1alpha3", func(t *testing.T) { ro := fakeRollout("stable-service", "canary-service", "root-service", "traffic-split-name") client := fake.NewSimpleClientset() - SetSMIAPIVersion("v1alpha3") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha3") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -215,8 +215,8 @@ func TestReconcilePatchExistingTrafficSplit(t *testing.T) { t.Run("v1alpha2", func(t *testing.T) { ts2 := trafficSplitV1Alpha2(ro, objectMeta, "root-service", int32(10)) client := fake.NewSimpleClientset(ts2) - SetSMIAPIVersion("v1alpha2") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha2") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -247,8 +247,8 @@ func TestReconcilePatchExistingTrafficSplit(t *testing.T) { t.Run("v1alpha3", func(t *testing.T) { ts3 := trafficSplitV1Alpha3(ro, objectMeta, "root-service", int32(10)) client := fake.NewSimpleClientset(ts3) - SetSMIAPIVersion("v1alpha3") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha3") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -307,8 +307,8 @@ func TestReconcilePatchExistingTrafficSplitNoChange(t *testing.T) { objMeta := objectMeta("traffic-split-v1alpha2", ro, schema.GroupVersionKind{}) ts2 := trafficSplitV1Alpha2(ro, objMeta, "root-service", int32(10)) client := fake.NewSimpleClientset(ts2) - SetSMIAPIVersion("v1alpha2") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha2") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -332,8 +332,8 @@ func TestReconcilePatchExistingTrafficSplitNoChange(t *testing.T) { objMeta := objectMeta("traffic-split-v1alpha3", ro, schema.GroupVersionKind{}) ts3 := trafficSplitV1Alpha3(ro, objMeta, "root-service", int32(10)) client := fake.NewSimpleClientset(ts3) - SetSMIAPIVersion("v1alpha3") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha3") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) r, err := NewReconciler(ReconcilerConfig{ Rollout: ro, Client: client, @@ -397,8 +397,8 @@ func TestReconcileRolloutDoesNotOwnTrafficSplitError(t *testing.T) { t.Run("v1alpha2", func(t *testing.T) { ts2 := trafficSplitV1Alpha2(ro, objMeta, "root-service", int32(10)) ts2.OwnerReferences = nil - SetSMIAPIVersion("v1alpha2") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha2") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) client := fake.NewSimpleClientset(ts2) r, err := NewReconciler(ReconcilerConfig{ @@ -416,8 +416,8 @@ func TestReconcileRolloutDoesNotOwnTrafficSplitError(t *testing.T) { t.Run("v1alpha3", func(t *testing.T) { ts3 := trafficSplitV1Alpha3(ro, objMeta, "root-service", int32(10)) ts3.OwnerReferences = nil - SetSMIAPIVersion("v1alpha3") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha3") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) client := fake.NewSimpleClientset(ts3) r, err := NewReconciler(ReconcilerConfig{ @@ -490,8 +490,8 @@ func TestCreateTrafficSplitForMultipleBackends(t *testing.T) { }) t.Run("v1alpha2", func(t *testing.T) { - SetSMIAPIVersion("v1alpha2") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha2") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) client := fake.NewSimpleClientset() r, err := NewReconciler(ReconcilerConfig{ @@ -534,8 +534,8 @@ func TestCreateTrafficSplitForMultipleBackends(t *testing.T) { }) t.Run("v1alpha3", func(t *testing.T) { - SetSMIAPIVersion("v1alpha3") - defer SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) + defaults.SetSMIAPIVersion("v1alpha3") + defer defaults.SetSMIAPIVersion(defaults.DefaultSMITrafficSplitVersion) client := fake.NewSimpleClientset() r, err := NewReconciler(ReconcilerConfig{ diff --git a/rollout/trafficrouting_test.go b/rollout/trafficrouting_test.go index 57fb408f27..e5214ccf99 100644 --- a/rollout/trafficrouting_test.go +++ b/rollout/trafficrouting_test.go @@ -470,7 +470,9 @@ func TestNewTrafficRoutingReconciler(t *testing.T) { } // Verifies with a canary using traffic routing, we add a scaledown delay to the old ReplicaSet -// after promoting desired ReplicaSet to stable +// after promoting desired ReplicaSet to stable. +// NOTE: As of v1.1, scale down delays are added to ReplicaSets on *subsequent* reconciliations +// after the desired RS has been promoted to stable func TestCanaryWithTrafficRoutingAddScaleDownDelay(t *testing.T) { f := newFixture(t) defer f.Close() @@ -483,30 +485,27 @@ func TestCanaryWithTrafficRoutingAddScaleDownDelay(t *testing.T) { } r2 := bumpVersion(r1) rs1 := newReplicaSetWithStatus(r1, 1, 1) - rs1PodHash := rs1.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] rs2 := newReplicaSetWithStatus(r2, 1, 1) - r2 = updateCanaryRolloutStatus(r2, rs1PodHash, 2, 2, 2, false) 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) - canarySelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} - stableSelector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs1PodHash} - canarySvc := newService("canary", 80, canarySelector, r2) - stableSvc := newService("stable", 80, stableSelector, r2) + selector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rs2PodHash} + canarySvc := newService("canary", 80, selector, r2) + stableSvc := newService("stable", 80, selector, r2) f.kubeobjects = append(f.kubeobjects, rs1, rs2, canarySvc, stableSvc) f.replicaSetLister = append(f.replicaSetLister, rs1, rs2) f.rolloutLister = append(f.rolloutLister, r2) f.objects = append(f.objects, r2) - rs1Patch := f.expectPatchReplicaSetAction(rs1) // adds the annotation - patchIndex := f.expectPatchRolloutAction(r2) // updates the rollout status + rs1Patch := f.expectPatchReplicaSetAction(rs1) // set scale-down-deadline annotation f.run(getKey(r2, t)) f.verifyPatchedReplicaSet(rs1Patch, 30) - roPatchObj := f.getPatchedRolloutAsObject(patchIndex) - assert.Equal(t, rs2PodHash, roPatchObj.Status.StableRS) - assert.Nil(t, roPatchObj.Status.CurrentStepIndex) } // Verifies with a canary using traffic routing, we scale down old ReplicaSets which exceed our limit diff --git a/test/e2e/aws_test.go b/test/e2e/aws_test.go index 762a5ecc03..4190449aef 100644 --- a/test/e2e/aws_test.go +++ b/test/e2e/aws_test.go @@ -20,17 +20,28 @@ func TestAWSSuite(t *testing.T) { } // TestALBUpdate is a simple integration test which verifies the controller can work in a real AWS -// environment. It is intended to be run with the `--alb-verify-weight` controller flag. Success of +// environment. It is intended to be run with the `--aws-verify-target-group` controller flag. Success of // this test against a controller using that flag, indicates that the controller was able to perform // weight verification using AWS APIs. // This test will be skipped unless E2E_ALB_INGESS_ANNOTATIONS is set (can be an empty struct). e.g.: -// make test-e2e E2E_INSTANCE_ID= E2E_TEST_OPTIONS="-testify.m TestALBUpdate$" E2E_ALB_INGESS_ANNOTATIONS='{"kubernetes.io/ingress.class": "aws-alb", "alb.ingress.kubernetes.io/security-groups": "iks-intuit-cidr-ingress-tcp-443"}' -func (s *AWSSuite) TestALBUpdate() { +// make test-e2e E2E_TEST_OPTIONS="-testify.m TestALBCanaryUpdate$" E2E_IMAGE_PREFIX="docker.intuit.com/docker-rmt/" E2E_INSTANCE_ID= E2E_ALB_INGESS_ANNOTATIONS='{"kubernetes.io/ingress.class": "aws-alb", "alb.ingress.kubernetes.io/security-groups": "iks-intuit-cidr-ingress-tcp-443"}' +func (s *AWSSuite) TestALBCanaryUpdate() { if val, _ := os.LookupEnv(fixtures.EnvVarE2EALBIngressAnnotations); val == "" { s.T().SkipNow() } s.Given(). - HealthyRollout(`@functional/alb-rollout.yaml`). + HealthyRollout(`@functional/alb-canary-rollout.yaml`). + When(). + UpdateSpec(). + WaitForRolloutStatus("Healthy") +} + +func (s *AWSSuite) TestALBBlueGreenUpdate() { + if val, _ := os.LookupEnv(fixtures.EnvVarE2EALBIngressAnnotations); val == "" { + s.T().SkipNow() + } + s.Given(). + HealthyRollout(`@functional/alb-bluegreen-rollout.yaml`). When(). UpdateSpec(). WaitForRolloutStatus("Healthy") diff --git a/test/e2e/canary_test.go b/test/e2e/canary_test.go index 07fb2e523d..723fc9cddd 100644 --- a/test/e2e/canary_test.go +++ b/test/e2e/canary_test.go @@ -477,6 +477,7 @@ spec: annotations: rev: two`). // update to revision 2 WaitForRolloutStatus("Healthy"). + Sleep(2 * time.Second). // sleep is necessary since scale down delay annotation happens in s subsequent reconciliation Then(). Assert(func(t *fixtures.Then) { rs1 := t.GetReplicaSetByRevision("1") diff --git a/test/e2e/functional/alb-bluegreen-rollout.yaml b/test/e2e/functional/alb-bluegreen-rollout.yaml new file mode 100644 index 0000000000..e06c2b5bea --- /dev/null +++ b/test/e2e/functional/alb-bluegreen-rollout.yaml @@ -0,0 +1,71 @@ +apiVersion: v1 +kind: Service +metadata: + name: alb-bluegreen-desired +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-bluegreen +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-bluegreen-stable +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-bluegreen +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: alb-bluegreen-ingress + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + backend: + serviceName: alb-bluegreen-stable + servicePort: 80 +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-bluegreen +spec: + selector: + matchLabels: + app: alb-bluegreen + template: + metadata: + labels: + app: alb-bluegreen + spec: + containers: + - name: alb-bluegreen + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + blueGreen: + previewService: alb-bluegreen-desired + activeService: alb-bluegreen-stable diff --git a/test/e2e/functional/alb-rollout.yaml b/test/e2e/functional/alb-canary-rollout.yaml similarity index 71% rename from test/e2e/functional/alb-rollout.yaml rename to test/e2e/functional/alb-canary-rollout.yaml index ad8157b3c7..df90ecc3cd 100644 --- a/test/e2e/functional/alb-rollout.yaml +++ b/test/e2e/functional/alb-canary-rollout.yaml @@ -1,7 +1,7 @@ apiVersion: v1 kind: Service metadata: - name: alb-rollout-root + name: alb-canary-root spec: type: NodePort ports: @@ -10,12 +10,12 @@ spec: protocol: TCP name: http selector: - app: alb-rollout + app: alb-canary --- apiVersion: v1 kind: Service metadata: - name: alb-rollout-canary + name: alb-canary-desired spec: type: NodePort ports: @@ -24,12 +24,12 @@ spec: protocol: TCP name: http selector: - app: alb-rollout + app: alb-canary --- apiVersion: v1 kind: Service metadata: - name: alb-rollout-stable + name: alb-canary-stable spec: type: NodePort ports: @@ -38,12 +38,12 @@ spec: protocol: TCP name: http selector: - app: alb-rollout + app: alb-canary --- apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: - name: alb-rollout-ingress + name: alb-canary-ingress annotations: kubernetes.io/ingress.class: alb spec: @@ -52,24 +52,24 @@ spec: paths: - path: /* backend: - serviceName: alb-rollout-root + serviceName: alb-canary-root servicePort: use-annotation --- apiVersion: argoproj.io/v1alpha1 kind: Rollout metadata: - name: alb-rollout + name: alb-canary spec: selector: matchLabels: - app: alb-rollout + app: alb-canary template: metadata: labels: - app: alb-rollout + app: alb-canary spec: containers: - - name: alb-rollout + - name: alb-canary image: nginx:1.19-alpine ports: - name: http @@ -81,15 +81,15 @@ spec: cpu: 5m strategy: canary: - canaryService: alb-rollout-canary - stableService: alb-rollout-stable + canaryService: alb-canary-desired + stableService: alb-canary-stable trafficRouting: alb: - ingress: alb-rollout-ingress - rootService: alb-rollout-root + ingress: alb-canary-ingress + rootService: alb-canary-root servicePort: 80 steps: - setWeight: 10 - pause: {duration: 5s} - setWeight: 20 - - pause: {duration: 5s} + - pause: {duration: 5s} \ No newline at end of file diff --git a/test/fixtures/e2e_suite.go b/test/fixtures/e2e_suite.go index 71589276cf..e7ac739ab6 100644 --- a/test/fixtures/e2e_suite.go +++ b/test/fixtures/e2e_suite.go @@ -39,6 +39,8 @@ const ( // E2E_POD_DELAY slows down pod startup and shutdown by the value in seconds (default: 0) // Used humans slow down rollout activity during a test EnvVarE2EPodDelay = "E2E_POD_DELAY" + // EnvVarE2EImagePrefix is a prefix that will be prefixed to images used by the e2e tests + EnvVarE2EImagePrefix = "E2E_IMAGE_PREFIX" // E2E_DEBUG makes e2e testing easier to debug by not tearing down the suite EnvVarE2EDebug = "E2E_DEBUG" // E2E_ALB_INGESS_ANNOTATIONS is a map of annotations to apply to ingress for AWS Load Balancer Controller diff --git a/test/fixtures/when.go b/test/fixtures/when.go index 81d44628e9..3820a6afd7 100644 --- a/test/fixtures/when.go +++ b/test/fixtures/when.go @@ -53,8 +53,9 @@ func (w *When) ApplyManifests(yaml ...string) *When { objects = w.parseTextToObjects(yaml[0]) } for _, obj := range objects { - if obj.GetKind() == "Rollout" && E2EPodDelay > 0 { + if obj.GetKind() == "Rollout" { w.injectDelays(obj) + w.injectImagePrefix(obj) } if obj.GetKind() == "Ingress" { w.injectIngressAnnotations(obj) @@ -72,6 +73,9 @@ func (w *When) DeleteObject(kind, name string) *When { // injectDelays adds postStart/preStop handlers to slow down readiness/termination by adding a // preStart and postStart handlers which sleeps for the specified duration. func (w *When) injectDelays(un *unstructured.Unstructured) { + if E2EPodDelay == 0 { + return + } sleepHandler := corev1.Handler{ Exec: &corev1.ExecAction{ Command: []string{"sleep", strconv.Itoa(E2EPodDelay)}, @@ -92,6 +96,21 @@ func (w *When) injectDelays(un *unstructured.Unstructured) { w.CheckError(err) } +// injectImagePrefix prefixes images used in tests with a prefix. Useful if container registries are blocked +func (w *When) injectImagePrefix(un *unstructured.Unstructured) { + imagePrefix := os.Getenv(EnvVarE2EImagePrefix) + if imagePrefix == "" { + return + } + containersIf, _, err := unstructured.NestedSlice(un.Object, "spec", "template", "spec", "containers") + w.CheckError(err) + container := containersIf[0].(map[string]interface{}) + container["image"] = imagePrefix + container["image"].(string) + containersIf[0] = container + err = unstructured.SetNestedSlice(un.Object, containersIf, "spec", "template", "spec", "containers") + w.CheckError(err) +} + // injectIngressAnnotations injects ingress annotations defined in environment variables. Currently // E2E_ALB_INGESS_ANNOTATIONS func (w *When) injectIngressAnnotations(un *unstructured.Unstructured) { diff --git a/test/util/util.go b/test/util/util.go index 234e006d00..a783107ebf 100644 --- a/test/util/util.go +++ b/test/util/util.go @@ -40,15 +40,21 @@ func NewFakeDynamicClient(objects ...runtime.Object) *dynamicfake.FakeDynamicCli scheme := runtime.NewScheme() vsvcGVR := istioutil.GetIstioVirtualServiceGVR() druleGVR := istioutil.GetIstioDestinationRuleGVR() + tgbGVR := schema.GroupVersionResource{ + Group: "elbv2.k8s.aws", + Version: "v1beta1", + Resource: "targetgroupbindings", + } listMapping := map[schema.GroupVersionResource]string{ - vsvcGVR: vsvcGVR.Resource + "List", - druleGVR: druleGVR.Resource + "List", + vsvcGVR: "VirtualServiceList", + druleGVR: "DestinationRuleList", v1alpha1.RolloutGVR: rollouts.RolloutKind + "List", v1alpha1.AnalysisTemplateGVR: rollouts.AnalysisTemplateKind + "List", v1alpha1.AnalysisRunGVR: rollouts.AnalysisRunKind + "List", v1alpha1.ExperimentGVR: rollouts.ExperimentKind + "List", v1alpha1.ClusterAnalysisTemplateGVR: rollouts.ClusterAnalysisTemplateKind + "List", + tgbGVR: "TargetGroupBindingList", } return dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping, objects...) } diff --git a/utils/aws/aws.go b/utils/aws/aws.go index 202049b1d2..ad59b41985 100644 --- a/utils/aws/aws.go +++ b/utils/aws/aws.go @@ -2,12 +2,19 @@ package aws import ( "context" + "encoding/json" "fmt" + "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/aws/aws-sdk-go-v2/config" elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/dynamic" ) // AWSLoadBalancerV2TagKeyResourceID is the tag applied to an AWS resource by the AWS Load Balancer @@ -15,11 +22,23 @@ import ( // controller to identify the correct TargetGroups associated with the LoadBalancer. For AWS // target group service references, the format is: /-: // Example: ingress.k8s.aws/resource: default/alb-rollout-ingress-alb-rollout-stable:80 -// See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/guide/ingress/annotations/#resource-tags -// https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/2e51fbdc5dc978d66e36b376d6dc56b0ae146d8f/internal/alb/generator/tag.go#L125-L128 +// See: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/ingress/annotations/#resource-tags +// https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/da8951f80521651e0a1ffe1361c011d6baad7706/pkg/deploy/tracking/provider.go#L19 const AWSLoadBalancerV2TagKeyResourceID = "ingress.k8s.aws/resource" +// TargetType is the targetType of your ELBV2 TargetGroup. +// +// * with `instance` TargetType, nodes with nodePort for your service will be registered as targets +// * with `ip` TargetType, Pods with containerPort for your service will be registered as targets +type TargetType string + +const ( + TargetTypeInstance TargetType = "instance" + TargetTypeIP TargetType = "ip" +) + type Client interface { + GetTargetGroupHealth(ctx context.Context, targetGroupARN string) ([]elbv2types.TargetHealthDescription, error) GetTargetGroupMetadata(ctx context.Context, loadBalancerARN string) ([]TargetGroupMeta, error) FindLoadBalancerByDNSName(ctx context.Context, dnsName string) (*elbv2types.LoadBalancer, error) } @@ -29,11 +48,13 @@ type ELBv2APIClient interface { elbv2.DescribeTargetGroupsAPIClient elbv2.DescribeLoadBalancersAPIClient elbv2.DescribeListenersAPIClient + DescribeTargetHealth(ctx context.Context, params *elbv2.DescribeTargetHealthInput, optFns ...func(*elbv2.Options)) (*elbv2.DescribeTargetHealthOutput, error) DescribeRules(ctx context.Context, params *elbv2.DescribeRulesInput, optFns ...func(*elbv2.Options)) (*elbv2.DescribeRulesOutput, error) DescribeTags(ctx context.Context, params *elbv2.DescribeTagsInput, optFns ...func(*elbv2.Options)) (*elbv2.DescribeTagsOutput, error) } -type client struct { +// ClientAdapter implements the Client interface +type ClientAdapter struct { ELBV2 ELBv2APIClient // loadBalancerDNStoARN is a cache that maps a LoadBalancer DNSName to an ARN @@ -48,19 +69,64 @@ type TargetGroupMeta struct { Weight *int32 } -func NewClient() (Client, error) { +// TargetGroupBinding is the Schema for the TargetGroupBinding API +// This is a subset of actual type definition and should only be used for readonly operations +// https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/v2.2.1/apis/elbv2/v1beta1/targetgroupbinding_types.go +type TargetGroupBinding struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TargetGroupBindingSpec `json:"spec,omitempty"` +} + +// TargetGroupBindingSpec defines the desired state of TargetGroupBinding +type TargetGroupBindingSpec struct { + // targetGroupARN is the Amazon Resource Name (ARN) for the TargetGroup. + TargetGroupARN string `json:"targetGroupARN"` + + // targetType is the TargetType of TargetGroup. If unspecified, it will be automatically inferred. + // +optional + TargetType *TargetType `json:"targetType,omitempty"` + + // serviceRef is a reference to a Kubernetes Service and ServicePort. + ServiceRef ServiceReference `json:"serviceRef"` +} + +// ServiceReference defines reference to a Kubernetes Service and its ServicePort. +type ServiceReference struct { + // Name is the name of the Service. + Name string `json:"name"` + + // Port is the port of the ServicePort. + Port intstr.IntOrString `json:"port"` +} + +// NewClient instantiates a new AWS Client. It is declared as a variable to allow mocking +var NewClient = DefaultNewClientFunc + +func DefaultNewClientFunc() (Client, error) { cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { return nil, err } - c := client{ + c := ClientAdapter{ ELBV2: elbv2.NewFromConfig(cfg), loadBalancerDNStoARN: make(map[string]string), } return &c, nil } -func (c *client) FindLoadBalancerByDNSName(ctx context.Context, dnsName string) (*elbv2types.LoadBalancer, error) { +func FakeNewClientFunc(elbClient ELBv2APIClient) func() (Client, error) { + return func() (Client, error) { + c := ClientAdapter{ + ELBV2: elbClient, + loadBalancerDNStoARN: make(map[string]string), + } + return &c, nil + } +} + +func (c *ClientAdapter) FindLoadBalancerByDNSName(ctx context.Context, dnsName string) (*elbv2types.LoadBalancer, error) { lbOutput, err := c.ELBV2.DescribeLoadBalancers(ctx, &elbv2.DescribeLoadBalancersInput{}) if err != nil { return nil, err @@ -75,7 +141,7 @@ func (c *client) FindLoadBalancerByDNSName(ctx context.Context, dnsName string) // GetTargetGroupMetadata is a convenience to retrieve the target groups of a load balancer along // with relevant metadata (tags, and traffic weights). -func (c *client) GetTargetGroupMetadata(ctx context.Context, loadBalancerARN string) ([]TargetGroupMeta, error) { +func (c *ClientAdapter) GetTargetGroupMetadata(ctx context.Context, loadBalancerARN string) ([]TargetGroupMeta, error) { // Get target groups associated with LoadBalancer tgIn := elbv2.DescribeTargetGroupsInput{ LoadBalancerArn: &loadBalancerARN, @@ -154,8 +220,173 @@ func (c *client) GetTargetGroupMetadata(ctx context.Context, loadBalancerARN str return tgMeta, nil } -// BuildV2TargetGroupID returns the AWS targetGroup ResourceID that compatible with V2 version. -// Copied from https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/2e51fbdc5dc978d66e36b376d6dc56b0ae146d8f/internal/alb/generator/tag.go#L125-L128 -func BuildV2TargetGroupID(namespace string, ingressName string, serviceName string, servicePort int32) string { +// GetTargetGroupHealth returns health descriptions of registered targets in a target group. +// A TargetHealthDescription is an IP:port pair, along with its health status. +func (c *ClientAdapter) GetTargetGroupHealth(ctx context.Context, targetGroupARN string) ([]elbv2types.TargetHealthDescription, error) { + thIn := elbv2.DescribeTargetHealthInput{ + TargetGroupArn: &targetGroupARN, + } + thOut, err := c.ELBV2.DescribeTargetHealth(ctx, &thIn) + if err != nil { + return nil, err + } + return thOut.TargetHealthDescriptions, nil +} + +// BuildTargetGroupResourceID returns the AWS TargetGroup ResourceID +// Adapted from https://github.com/kubernetes-sigs/aws-load-balancer-controller/blob/57c8ce344fe09089fa2e20b0aa9fc0972696bc05/pkg/ingress/model_build_target_group.go#L398-L400 +func BuildTargetGroupResourceID(namespace string, ingressName string, serviceName string, servicePort int32) string { return fmt.Sprintf("%s/%s-%s:%d", namespace, ingressName, serviceName, servicePort) } + +func GetTargetGroupBindingsGVR() (schema.GroupVersionResource, error) { + gv, err := schema.ParseGroupVersion(defaults.GetTargetGroupBindingAPIVersion()) + if err != nil { + return schema.GroupVersionResource{}, err + } + return schema.GroupVersionResource{ + Group: gv.Group, + Version: gv.Version, + Resource: "targetgroupbindings", + }, nil +} + +func GetTargetGroupBindingsByService(ctx context.Context, dynamicClient dynamic.Interface, svc corev1.Service) ([]TargetGroupBinding, error) { + gvr, err := GetTargetGroupBindingsGVR() + if err != nil { + return nil, err + } + tgbList, err := dynamicClient.Resource(gvr).Namespace(svc.Namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + var tgbs []TargetGroupBinding + for _, tgbUn := range tgbList.Items { + tgb, err := toTargetGroupBinding(tgbUn.Object) + if err != nil { + return nil, err + } + if tgb.Spec.ServiceRef.Name != svc.Name { + continue + } + for _, port := range svc.Spec.Ports { + if tgb.Spec.ServiceRef.Port.Type == intstr.Int && port.Port == tgb.Spec.ServiceRef.Port.IntVal { + tgbs = append(tgbs, *tgb) + break + } else if tgb.Spec.ServiceRef.Port.StrVal != "" && port.Name == tgb.Spec.ServiceRef.Port.StrVal { + tgbs = append(tgbs, *tgb) + break + } + } + } + return tgbs, nil +} + +func toTargetGroupBinding(obj map[string]interface{}) (*TargetGroupBinding, error) { + data, err := json.Marshal(obj) + if err != nil { + return nil, err + } + var tgb TargetGroupBinding + err = json.Unmarshal(data, &tgb) + if err != nil { + return nil, err + } + return &tgb, nil +} + +// getNumericPort resolves the numeric port which a AWS TargetGroup targets. +// This is needed in case the TargetGroupBinding's spec.serviceRef.Port is a string and not a number +// Returns 0 if unable to find matching port in given service. +func getNumericPort(tgb TargetGroupBinding, svc corev1.Service) int32 { + if portInt := tgb.Spec.ServiceRef.Port.IntValue(); portInt > 0 { + return int32(portInt) + } + // port is a string and not a num + for _, svcPort := range svc.Spec.Ports { + if tgb.Spec.ServiceRef.Port.StrVal == svcPort.Name { + return svcPort.Port + } + } + return 0 +} + +// TargetGroupVerifyResult returns metadata when a target group is verified. +type TargetGroupVerifyResult struct { + Service string + Verified bool + EndpointsRegistered int + EndpointsTotal int +} + +// VerifyTargetGroupBinding verifies if the underlying AWS TargetGroup has all Pod IPs and ports +// from the given service (the K8s Endpoints list) registered to the TargetGroup. +// NOTE: a previous version of this method used to additionally verify that all registered targets +// were "healthy" (in addition to registered), but the health of registered targets is actually +// irrelevant for our purposes of verifying the service label change was reflected in the LB. +// Returns nil if the verification is not applicable (e.g. target type is not IP) +func VerifyTargetGroupBinding(ctx context.Context, logCtx *log.Entry, awsClnt Client, tgb TargetGroupBinding, endpoints *corev1.Endpoints, svc *corev1.Service) (*TargetGroupVerifyResult, error) { + if tgb.Spec.TargetType == nil || *tgb.Spec.TargetType != TargetTypeIP { + // We only need to verify target groups using AWS CNI (spec.targetType: ip) + return nil, nil + } + port := getNumericPort(tgb, *svc) + if port == 0 { + logCtx.Warn("Unable to match TargetGroupBinding spec.serviceRef.port to Service spec.ports") + return nil, nil + } + logCtx = logCtx.WithFields(map[string]interface{}{ + "service": svc.Name, + "targetgroupbinding": tgb.Name, + "tg": tgb.Spec.TargetGroupARN, + "port": port, + }) + targets, err := awsClnt.GetTargetGroupHealth(ctx, tgb.Spec.TargetGroupARN) + if err != nil { + return nil, err + } + + // Remember/initialize all of the ip:port of the endpoints list that we expect to see registered + endpointIPs := make(map[string]bool) + for _, subset := range endpoints.Subsets { + for _, addr := range subset.Addresses { + endpointIPs[fmt.Sprintf("%s:%d", addr.IP, port)] = false + } + } + + logCtx.Infof("verifying %d endpoint addresses (of %d targets)", len(endpointIPs), len(targets)) + + // Iterate all registered targets in AWS TargetGroup. Mark all endpoint IPs which we see registered + for _, target := range targets { + if target.Target == nil || target.Target.Id == nil || target.Target.Port == nil { + logCtx.Warnf("Invalid target in TargetGroup: %v", target) + continue + } + targetStr := fmt.Sprintf("%s:%d", *target.Target.Id, *target.Target.Port) + _, isEndpointTarget := endpointIPs[targetStr] + if !isEndpointTarget { + // this is a target for something not in the endpoint list (e.g. old endpoint entry). Ignore it + continue + } + // Verify we see the endpoint IP registered to the TargetGroup + // NOTE: we used to check health here, but health is not relevant for verifying service label change + endpointIPs[targetStr] = true + } + + tgvr := TargetGroupVerifyResult{ + Service: svc.Name, + EndpointsTotal: len(endpointIPs), + EndpointsRegistered: 0, + } + + // Check if any of our desired endpoints are not yet registered + for epIP, seen := range endpointIPs { + if !seen { + logCtx.Infof("Service endpoint IP %s not yet registered", epIP) + } else { + tgvr.EndpointsRegistered++ + } + } + tgvr.Verified = bool(tgvr.EndpointsRegistered == tgvr.EndpointsTotal) + return &tgvr, nil +} diff --git a/utils/aws/aws_test.go b/utils/aws/aws_test.go index 1e9904e083..94836f25bb 100644 --- a/utils/aws/aws_test.go +++ b/utils/aws/aws_test.go @@ -5,21 +5,24 @@ import ( "testing" elbv2 "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" - "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" + log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + testutil "github.com/argoproj/argo-rollouts/test/util" "github.com/argoproj/argo-rollouts/utils/aws/mocks" + unstructuredutil "github.com/argoproj/argo-rollouts/utils/unstructured" ) func newFakeClient() (*mocks.ELBv2APIClient, Client) { fakeELB := mocks.ELBv2APIClient{} - c := client{ - ELBV2: &fakeELB, - loadBalancerDNStoARN: make(map[string]string), - } - return &fakeELB, &c + awsClient, _ := FakeNewClientFunc(&fakeELB)() + return &fakeELB, awsClient } func TestFindLoadBalancerByDNSName(t *testing.T) { @@ -36,12 +39,12 @@ func TestFindLoadBalancerByDNSName(t *testing.T) { { fakeELB, c := newFakeClient() // Mock output - expectedLB := types.LoadBalancer{ + expectedLB := elbv2types.LoadBalancer{ LoadBalancerArn: pointer.StringPtr("lb-abc123"), DNSName: pointer.StringPtr("find-loadbalancer-test-abc-123.us-west-2.elb.amazonaws.com"), } lbOut := elbv2.DescribeLoadBalancersOutput{ - LoadBalancers: []types.LoadBalancer{ + LoadBalancers: []elbv2types.LoadBalancer{ expectedLB, }, } @@ -59,7 +62,7 @@ func TestGetTargetGroupMetadata(t *testing.T) { // mock the output tgOut := elbv2.DescribeTargetGroupsOutput{ - TargetGroups: []types.TargetGroup{ + TargetGroups: []elbv2types.TargetGroup{ { TargetGroupArn: pointer.StringPtr("tg-abc123"), }, @@ -71,10 +74,10 @@ func TestGetTargetGroupMetadata(t *testing.T) { fakeELB.On("DescribeTargetGroups", mock.Anything, mock.Anything).Return(&tgOut, nil) tagsOut := elbv2.DescribeTagsOutput{ - TagDescriptions: []types.TagDescription{ + TagDescriptions: []elbv2types.TagDescription{ { ResourceArn: pointer.StringPtr("tg-abc123"), - Tags: []types.Tag{ + Tags: []elbv2types.Tag{ { Key: pointer.StringPtr("foo"), Value: pointer.StringPtr("bar"), @@ -86,7 +89,7 @@ func TestGetTargetGroupMetadata(t *testing.T) { fakeELB.On("DescribeTags", mock.Anything, mock.Anything).Return(&tagsOut, nil) listenersOut := elbv2.DescribeListenersOutput{ - Listeners: []types.Listener{ + Listeners: []elbv2types.Listener{ { ListenerArn: pointer.StringPtr("lst-abc123"), LoadBalancerArn: pointer.StringPtr("lb-abc123"), @@ -96,12 +99,12 @@ func TestGetTargetGroupMetadata(t *testing.T) { fakeELB.On("DescribeListeners", mock.Anything, mock.Anything).Return(&listenersOut, nil) rulesOut := elbv2.DescribeRulesOutput{ - Rules: []types.Rule{ + Rules: []elbv2types.Rule{ { - Actions: []types.Action{ + Actions: []elbv2types.Action{ { - ForwardConfig: &types.ForwardActionConfig{ - TargetGroups: []types.TargetGroupTuple{ + ForwardConfig: &elbv2types.ForwardActionConfig{ + TargetGroups: []elbv2types.TargetGroupTuple{ { TargetGroupArn: pointer.StringPtr("tg-abc123"), Weight: pointer.Int32Ptr(10), @@ -128,6 +131,203 @@ func TestGetTargetGroupMetadata(t *testing.T) { assert.Nil(t, tgMeta[1].Weight) } -func TestBuildV2TargetGroupID(t *testing.T) { - assert.Equal(t, "default/ingress-svc:80", BuildV2TargetGroupID("default", "ingress", "svc", 80)) +func TestBuildTargetGroupResourceID(t *testing.T) { + assert.Equal(t, "default/ingress-svc:80", BuildTargetGroupResourceID("default", "ingress", "svc", 80)) +} + +func TestGetTargetGroupHealth(t *testing.T) { + fakeELB, c := newFakeClient() + expectedHealth := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + HealthCheckPort: pointer.StringPtr("80"), + Target: &elbv2types.TargetDescription{}, + TargetHealth: &elbv2types.TargetHealth{ + State: elbv2types.TargetHealthStateEnumHealthy, + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&expectedHealth, nil) + + // Test + health, err := c.GetTargetGroupHealth(context.TODO(), "tg-abc123") + assert.NoError(t, err) + assert.Equal(t, expectedHealth.TargetHealthDescriptions, health) +} + +var testTargetGroupBinding = ` +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: active + namespace: default +spec: + serviceRef: + name: active + port: 80 + targetGroupARN: arn::1234 + targetType: ip +` + +func TestGetTargetGroupBindingsByService(t *testing.T) { + { + svc1 := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active", + Namespace: metav1.NamespaceDefault, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{}, + Ports: []corev1.ServicePort{{ + Protocol: "TCP", + Port: int32(80), + TargetPort: intstr.FromInt(80), + }}, + }, + } + obj := unstructuredutil.StrToUnstructuredUnsafe(testTargetGroupBinding) + dynamicClientSet := testutil.NewFakeDynamicClient(obj) + tgbs, err := GetTargetGroupBindingsByService(context.TODO(), dynamicClientSet, svc1) + assert.NoError(t, err) + assert.Equal(t, 80, tgbs[0].Spec.ServiceRef.Port.IntValue()) + assert.Equal(t, "arn::1234", tgbs[0].Spec.TargetGroupARN) + assert.Equal(t, "ip", string(*tgbs[0].Spec.TargetType)) + } + { + svc2 := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active", + Namespace: metav1.NamespaceDefault, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{}, + Ports: []corev1.ServicePort{{ + Protocol: "TCP", + Name: "foo", + TargetPort: intstr.FromInt(80), + }}, + }, + } + obj := unstructuredutil.StrToUnstructuredUnsafe(` +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: active + namespace: default +spec: + serviceRef: + name: active + port: foo + targetGroupARN: arn::1234 + targetType: instance +`) + dynamicClientSet := testutil.NewFakeDynamicClient(obj) + tgbs, err := GetTargetGroupBindingsByService(context.TODO(), dynamicClientSet, svc2) + assert.NoError(t, err) + assert.Equal(t, "foo", tgbs[0].Spec.ServiceRef.Port.StrVal) + assert.Equal(t, "arn::1234", tgbs[0].Spec.TargetGroupARN) + assert.Equal(t, "instance", string(*tgbs[0].Spec.TargetType)) + } +} + +func TestVerifyTargetGroupBindingIgnoreInstanceMode(t *testing.T) { + logCtx := log.NewEntry(log.New()) + _, awsClnt := newFakeClient() + tgb := TargetGroupBinding{ + Spec: TargetGroupBindingSpec{ + TargetType: (*TargetType)(pointer.StringPtr("instance")), + }, + } + res, err := VerifyTargetGroupBinding(context.TODO(), logCtx, awsClnt, tgb, nil, nil) + assert.Nil(t, res) + assert.NoError(t, err) +} + +func TestVerifyTargetGroupBinding(t *testing.T) { + logCtx := log.NewEntry(log.New()) + tgb := TargetGroupBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active", + Namespace: metav1.NamespaceDefault, + }, + Spec: TargetGroupBindingSpec{ + TargetType: (*TargetType)(pointer.StringPtr("ip")), + TargetGroupARN: "arn::1234", + }, + } + ep := corev1.Endpoints{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active", + Namespace: metav1.NamespaceDefault, + }, + Subsets: []corev1.EndpointSubset{ + { + Addresses: []corev1.EndpointAddress{ + { + IP: "1.2.3.4", // registered + }, + { + IP: "5.6.7.8", // registered + }, + { + IP: "2.4.6.8", // not registered + }, + }, + }, + }, + } + svc := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "active", + Namespace: metav1.NamespaceDefault, + }, + Spec: corev1.ServiceSpec{ + Selector: map[string]string{}, + Ports: []corev1.ServicePort{{ + Protocol: "TCP", + Port: int32(80), + TargetPort: intstr.FromInt(80), + }}, + }, + } + fakeELB, awsClnt := newFakeClient() + thOut := elbv2.DescribeTargetHealthOutput{ + TargetHealthDescriptions: []elbv2types.TargetHealthDescription{ + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("1.2.3.4"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("5.6.7.8"), + Port: pointer.Int32Ptr(80), + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("2.4.6.8"), // irrelevant + Port: pointer.Int32Ptr(81), // wrong port + }, + }, + { + Target: &elbv2types.TargetDescription{ + Id: pointer.StringPtr("9.8.7.6"), // irrelevant ip + Port: pointer.Int32Ptr(80), + }, + }, + }, + } + fakeELB.On("DescribeTargetHealth", mock.Anything, mock.Anything).Return(&thOut, nil) + res, err := VerifyTargetGroupBinding(context.TODO(), logCtx, awsClnt, tgb, &ep, &svc) + expectedRes := TargetGroupVerifyResult{ + Service: "active", + Verified: false, + EndpointsRegistered: 2, + EndpointsTotal: 3, + } + assert.Equal(t, expectedRes, *res) + assert.NoError(t, err) } diff --git a/utils/aws/mocks/ELBv2APIClient.go b/utils/aws/mocks/ELBv2APIClient.go index 8dae8549e5..7f621b0563 100644 --- a/utils/aws/mocks/ELBv2APIClient.go +++ b/utils/aws/mocks/ELBv2APIClient.go @@ -163,3 +163,33 @@ func (_m *ELBv2APIClient) DescribeTargetGroups(_a0 context.Context, _a1 *elastic return r0, r1 } + +// DescribeTargetHealth provides a mock function with given fields: ctx, params, optFns +func (_m *ELBv2APIClient) DescribeTargetHealth(ctx context.Context, params *elasticloadbalancingv2.DescribeTargetHealthInput, optFns ...func(*elasticloadbalancingv2.Options)) (*elasticloadbalancingv2.DescribeTargetHealthOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *elasticloadbalancingv2.DescribeTargetHealthOutput + if rf, ok := ret.Get(0).(func(context.Context, *elasticloadbalancingv2.DescribeTargetHealthInput, ...func(*elasticloadbalancingv2.Options)) *elasticloadbalancingv2.DescribeTargetHealthOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*elasticloadbalancingv2.DescribeTargetHealthOutput) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *elasticloadbalancingv2.DescribeTargetHealthInput, ...func(*elasticloadbalancingv2.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/utils/conditions/conditions.go b/utils/conditions/conditions.go index 0715c19fd0..40d173374a 100644 --- a/utils/conditions/conditions.go +++ b/utils/conditions/conditions.go @@ -135,6 +135,18 @@ const ( ServiceReferenceReason = "ServiceReferenceError" // ServiceReferencingManagedService is added in a rollout when the multiple rollouts reference a Rollout ServiceReferencingManagedService = "Service %q is managed by another Rollout" + + // TargetGroupHealthyReason is emitted when target group has been verified + TargetGroupVerifiedReason = "TargetGroupVerified" + TargetGroupVerifiedRegistrationMessage = "Service %s (TargetGroup %s) verified: %d endpoints registered" + TargetGroupVerifiedWeightsMessage = "Service %s (TargetGroup %s) verified: canary weight %d set" + // TargetGroupHealthyReason is emitted when target group has not been verified + TargetGroupUnverifiedReason = "TargetGroupUnverified" + TargetGroupUnverifiedRegistrationMessage = "Service %s (TargetGroup %s) not verified: %d/%d endpoints registered" + TargetGroupUnverifiedWeightsMessage = "Service %s (TargetGroup %s) not verified: canary weight %d not yet set (current: %d)" + // TargetGroupVerifyErrorReason is emitted when we fail to verify the health of a target group due to error + TargetGroupVerifyErrorReason = "TargetGroupVerifyError" + TargetGroupVerifyErrorMessage = "Failed to verify Service %s (TargetGroup %s): %s" ) // NewRolloutCondition creates a new rollout condition. diff --git a/utils/defaults/defaults.go b/utils/defaults/defaults.go index cd6e864a11..1d51a1493c 100644 --- a/utils/defaults/defaults.go +++ b/utils/defaults/defaults.go @@ -38,10 +38,19 @@ const ( ) const ( - DefaultAmbassadorAPIGroup = "getambassador.io" - DefaultAmbassadorVersion = "getambassador.io/v2" - DefaultIstioVersion = "v1alpha3" - DefaultSMITrafficSplitVersion = "v1alpha1" + DefaultAmbassadorAPIGroup = "getambassador.io" + DefaultAmbassadorVersion = "getambassador.io/v2" + DefaultIstioVersion = "v1alpha3" + DefaultSMITrafficSplitVersion = "v1alpha1" + DefaultTargetGroupBindingAPIVersion = "elbv2.k8s.aws/v1beta1" +) + +var ( + defaultVerifyTargetGroup = false + istioAPIVersion = DefaultIstioVersion + ambassadorAPIVersion = DefaultAmbassadorVersion + smiAPIVersion = DefaultSMITrafficSplitVersion + targetGroupBindingAPIVersion = DefaultTargetGroupBindingAPIVersion ) // GetReplicasOrDefault returns the deferenced number of replicas or the default number @@ -202,3 +211,45 @@ func Namespace() string { } return "argo-rollouts" } + +// SetDefaultVerifyTargetGroup sets the default setWeight verification when instantiating the reconciler +func SetVerifyTargetGroup(b bool) { + defaultVerifyTargetGroup = b +} + +// VerifyTargetGroup returns whether or not we should verify target groups +func VerifyTargetGroup() bool { + return defaultVerifyTargetGroup +} + +func SetIstioAPIVersion(apiVersion string) { + istioAPIVersion = apiVersion +} + +func GetIstioAPIVersion() string { + return istioAPIVersion +} + +func SetAmbassadorAPIVersion(apiVersion string) { + ambassadorAPIVersion = apiVersion +} + +func GetAmbassadorAPIVersion() string { + return ambassadorAPIVersion +} + +func SetSMIAPIVersion(apiVersion string) { + smiAPIVersion = apiVersion +} + +func GetSMIAPIVersion() string { + return smiAPIVersion +} + +func SetTargetGroupBindingAPIVersion(apiVersion string) { + targetGroupBindingAPIVersion = apiVersion +} + +func GetTargetGroupBindingAPIVersion() string { + return targetGroupBindingAPIVersion +} diff --git a/utils/defaults/defaults_test.go b/utils/defaults/defaults_test.go index a3e5322c3e..129fd1c959 100644 --- a/utils/defaults/defaults_test.go +++ b/utils/defaults/defaults_test.go @@ -355,3 +355,30 @@ func TestGetConsecutiveErrorLimitOrDefault(t *testing.T) { metricDefaultValue := &v1alpha1.Metric{} assert.Equal(t, DefaultConsecutiveErrorLimit, GetConsecutiveErrorLimitOrDefault(metricDefaultValue)) } + +func TestSetDefaults(t *testing.T) { + SetVerifyTargetGroup(true) + assert.True(t, VerifyTargetGroup()) + SetVerifyTargetGroup(false) + assert.False(t, VerifyTargetGroup()) + + SetIstioAPIVersion("v1alpha9") + assert.Equal(t, "v1alpha9", GetIstioAPIVersion()) + SetIstioAPIVersion(DefaultIstioVersion) + assert.Equal(t, DefaultIstioVersion, GetIstioAPIVersion()) + + SetAmbassadorAPIVersion("v1alpha9") + assert.Equal(t, "v1alpha9", GetAmbassadorAPIVersion()) + SetAmbassadorAPIVersion(DefaultAmbassadorVersion) + assert.Equal(t, DefaultAmbassadorVersion, GetAmbassadorAPIVersion()) + + SetSMIAPIVersion("v1alpha9") + assert.Equal(t, "v1alpha9", GetSMIAPIVersion()) + SetSMIAPIVersion(DefaultSMITrafficSplitVersion) + assert.Equal(t, DefaultSMITrafficSplitVersion, GetSMIAPIVersion()) + + SetTargetGroupBindingAPIVersion("v1alpha9") + assert.Equal(t, "v1alpha9", GetTargetGroupBindingAPIVersion()) + SetTargetGroupBindingAPIVersion(DefaultTargetGroupBindingAPIVersion) + assert.Equal(t, DefaultTargetGroupBindingAPIVersion, GetTargetGroupBindingAPIVersion()) +} diff --git a/utils/istio/istio.go b/utils/istio/istio.go index bc5e4a3c85..d0e45fb5fc 100644 --- a/utils/istio/istio.go +++ b/utils/istio/istio.go @@ -13,18 +13,6 @@ import ( "github.com/argoproj/argo-rollouts/utils/defaults" ) -var ( - istioAPIVersion = defaults.DefaultIstioVersion -) - -func SetIstioAPIVersion(apiVersion string) { - istioAPIVersion = apiVersion -} - -func GetIstioAPIVersion() string { - return istioAPIVersion -} - func DoesIstioExist(dynamicClient dynamic.Interface, namespace string) bool { _, err := dynamicClient.Resource(GetIstioVirtualServiceGVR()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{Limit: 1}) if err != nil { @@ -36,7 +24,7 @@ func DoesIstioExist(dynamicClient dynamic.Interface, namespace string) bool { func GetIstioVirtualServiceGVR() schema.GroupVersionResource { return schema.GroupVersionResource{ Group: "networking.istio.io", - Version: istioAPIVersion, + Version: defaults.GetIstioAPIVersion(), Resource: "virtualservices", } } @@ -44,7 +32,7 @@ func GetIstioVirtualServiceGVR() schema.GroupVersionResource { func GetIstioDestinationRuleGVR() schema.GroupVersionResource { return schema.GroupVersionResource{ Group: "networking.istio.io", - Version: istioAPIVersion, + Version: defaults.GetIstioAPIVersion(), Resource: "destinationrules", } } diff --git a/utils/istio/istio_test.go b/utils/istio/istio_test.go index 6eb78f0188..e6f8b78e71 100644 --- a/utils/istio/istio_test.go +++ b/utils/istio/istio_test.go @@ -7,6 +7,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1" + "github.com/argoproj/argo-rollouts/utils/defaults" "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" dynamicfake "k8s.io/client-go/dynamic/fake" @@ -39,12 +40,12 @@ func TestGetIstioVirtualServiceGVR(t *testing.T) { } func TestGetIstioDestinationRuleGVR(t *testing.T) { - SetIstioAPIVersion("v1alpha4") + defaults.SetIstioAPIVersion("v1alpha4") gvr := GetIstioDestinationRuleGVR() assert.Equal(t, "networking.istio.io", gvr.Group) assert.Equal(t, "v1alpha4", gvr.Version) assert.Equal(t, "destinationrules", gvr.Resource) - SetIstioAPIVersion("v1alpha3") + defaults.SetIstioAPIVersion("v1alpha3") } func TestGetRolloutVirtualServiceKeys(t *testing.T) { diff --git a/utils/rollout/rolloututil.go b/utils/rollout/rolloututil.go index e3eaab68eb..c2e997aa00 100644 --- a/utils/rollout/rolloututil.go +++ b/utils/rollout/rolloututil.go @@ -10,6 +10,12 @@ import ( "github.com/argoproj/argo-rollouts/utils/defaults" ) +// IsFullyPromoted returns whether or not the given rollout is in a fully promoted state. +// (versus being in the middle of an update). This is determined by checking if stable hash == desired hash +func IsFullyPromoted(ro *v1alpha1.Rollout) bool { + return ro.Status.StableRS == ro.Status.CurrentPodHash +} + // GetRolloutPhase returns a status and message for a rollout. Takes into consideration whether // or not metadata.generation was observed in status.observedGeneration // use this instead of CalculateRolloutPhase @@ -95,8 +101,15 @@ func CalculateRolloutPhase(spec v1alpha1.RolloutSpec, status v1alpha1.RolloutSta if ro.Status.BlueGreen.ActiveSelector == "" || ro.Status.BlueGreen.ActiveSelector != ro.Status.CurrentPodHash { return v1alpha1.RolloutPhaseProgressing, "active service cutover pending" } - if ro.Status.StableRS == "" || ro.Status.StableRS != ro.Status.CurrentPodHash { - return v1alpha1.RolloutPhaseProgressing, "waiting for analysis to complete" + if ro.Status.StableRS == "" || !IsFullyPromoted(&ro) { + // we switched the active selector to the desired ReplicaSet, but we have yet to mark it + // as stable. This could be caused by one of two things: + // 1. post-promotion analysis has yet to complete successfully + // 2. post-promotion verification (i.e. target group verification) + if waitingForBlueGreenPostPromotionAnalysis(&ro) { + return v1alpha1.RolloutPhaseProgressing, "waiting for analysis to complete" + } + return v1alpha1.RolloutPhaseProgressing, "waiting for post-promotion verification to complete" } } else if ro.Spec.Strategy.Canary != nil { if ro.Spec.Strategy.Canary.TrafficRouting == nil { @@ -107,13 +120,23 @@ func CalculateRolloutPhase(spec v1alpha1.RolloutSpec, status v1alpha1.RolloutSta return v1alpha1.RolloutPhaseProgressing, "old replicas are pending termination" } } - if ro.Status.StableRS == "" || ro.Status.StableRS != ro.Status.CurrentPodHash { + if ro.Status.StableRS == "" || !IsFullyPromoted(&ro) { return v1alpha1.RolloutPhaseProgressing, "waiting for all steps to complete" } } return v1alpha1.RolloutPhaseHealthy, "" } +// waitingForBlueGreenPostPromotionAnalysis returns we are waiting for blue-green post promotion to complete +func waitingForBlueGreenPostPromotionAnalysis(ro *v1alpha1.Rollout) bool { + if ro.Spec.Strategy.BlueGreen.PostPromotionAnalysis != nil { + if ro.Status.BlueGreen.PostPromotionAnalysisRunStatus == nil || !ro.Status.BlueGreen.PostPromotionAnalysisRunStatus.Status.Completed() { + return true + } + } + return false +} + // CanaryStepString returns a string representation of a canary step func CanaryStepString(c v1alpha1.CanaryStep) string { if c.SetWeight != nil { diff --git a/utils/rollout/rolloututil_test.go b/utils/rollout/rolloututil_test.go index 0a2071e5a9..4fc15a4c63 100644 --- a/utils/rollout/rolloututil_test.go +++ b/utils/rollout/rolloututil_test.go @@ -71,6 +71,27 @@ func newBlueGreenRollout() *v1alpha1.Rollout { } } +func TestIsFullyPromoted(t *testing.T) { + { + ro := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + StableRS: "abc123", + CurrentPodHash: "abc123", + }, + } + assert.True(t, IsFullyPromoted(ro)) + } + { + ro := &v1alpha1.Rollout{ + Status: v1alpha1.RolloutStatus{ + StableRS: "abc123", + CurrentPodHash: "def456", + }, + } + assert.False(t, IsFullyPromoted(ro)) + } +} + func TestRolloutStatusDegraded(t *testing.T) { ro := newCanaryRollout() ro.Status.Conditions = append(ro.Status.Conditions, v1alpha1.RolloutCondition{ @@ -137,6 +158,7 @@ func TestRolloutStatusProgressing(t *testing.T) { } { ro := newBlueGreenRollout() + ro.Spec.Strategy.BlueGreen.PostPromotionAnalysis = &v1alpha1.RolloutAnalysis{} ro.Status.BlueGreen.ActiveSelector = "def5678" ro.Status.StableRS = "abc1234" ro.Status.CurrentPodHash = "def5678" @@ -148,6 +170,19 @@ func TestRolloutStatusProgressing(t *testing.T) { assert.Equal(t, v1alpha1.RolloutPhaseProgressing, status) assert.Equal(t, "waiting for analysis to complete", message) } + { + ro := newBlueGreenRollout() + ro.Status.BlueGreen.ActiveSelector = "def5678" + ro.Status.StableRS = "abc1234" + ro.Status.CurrentPodHash = "def5678" + ro.Spec.Replicas = pointer.Int32Ptr(5) + ro.Status.Replicas = 5 + ro.Status.UpdatedReplicas = 5 + ro.Status.AvailableReplicas = 5 + status, message := GetRolloutPhase(ro) + assert.Equal(t, v1alpha1.RolloutPhaseProgressing, status) + assert.Equal(t, "waiting for post-promotion verification to complete", message) + } { // Scenario when a newly created rollout has partially filled in status (with hashes) // but no updated replica count From 89062e385809f9a854351145ac3918f12946bab5 Mon Sep 17 00:00:00 2001 From: Kareena Hirani Date: Thu, 26 Aug 2021 19:29:15 -0700 Subject: [PATCH 34/34] TrafficRouting ALB Step in Canary (#1394) * feat: ALB TrafficRouting with experiment step Signed-off-by: khhirani --- .../rollouts/v1alpha1/experiment_types.go | 2 +- pkg/apis/rollouts/validation/validation.go | 15 +- .../rollouts/validation/validation_test.go | 88 ++++++++ rollout/mocks/TrafficRoutingReconciler.go | 21 +- rollout/trafficrouting.go | 2 +- rollout/trafficrouting/alb/alb.go | 83 +++++-- rollout/trafficrouting/alb/alb_test.go | 202 +++++++++++++++++- .../trafficrouting/ambassador/ambassador.go | 2 +- rollout/trafficrouting/istio/istio.go | 2 +- rollout/trafficrouting/nginx/nginx.go | 2 +- rollout/trafficrouting/smi/smi.go | 2 +- rollout/trafficrouting/trafficroutingutil.go | 4 +- rollout/trafficrouting_test.go | 3 +- test/e2e/alb/rollout-alb-experiment.yaml | 97 +++++++++ test/e2e/aws_test.go | 59 +++++ test/fixtures/common.go | 13 +- 16 files changed, 553 insertions(+), 44 deletions(-) create mode 100644 test/e2e/alb/rollout-alb-experiment.yaml diff --git a/pkg/apis/rollouts/v1alpha1/experiment_types.go b/pkg/apis/rollouts/v1alpha1/experiment_types.go index 4f6c0a18f0..223ab08434 100644 --- a/pkg/apis/rollouts/v1alpha1/experiment_types.go +++ b/pkg/apis/rollouts/v1alpha1/experiment_types.go @@ -159,7 +159,7 @@ const ( // InvalidExperimentSpec means the experiment has an invalid spec and will not progress until // the spec is fixed. InvalidExperimentSpec ExperimentConditionType = "InvalidSpec" - // ExperimentConcluded means the experiment is available, ie. the active service is pointing at a + // ExperimentCompleted means the experiment is available, ie. the active service is pointing at a // replicaset with the required replicas up and running for at least minReadySeconds. ExperimentCompleted ExperimentConditionType = "Completed" // ExperimentProgressing means the experiment is progressing. Progress for a experiment is diff --git a/pkg/apis/rollouts/validation/validation.go b/pkg/apis/rollouts/validation/validation.go index 9c44d5e85a..3377fe4aee 100644 --- a/pkg/apis/rollouts/validation/validation.go +++ b/pkg/apis/rollouts/validation/validation.go @@ -28,6 +28,8 @@ const ( MissingFieldMessage = "Rollout has missing field '%s'" // InvalidSetWeightMessage indicates the setweight value needs to be between 0 and 100 InvalidSetWeightMessage = "SetWeight needs to be between 0 and 100" + // InvalidCanaryExperimentTemplateWeightWithoutTrafficRouting indicates experiment weight cannot be set without trafficRouting + InvalidCanaryExperimentTemplateWeightWithoutTrafficRouting = "Experiment template weight cannot be set unless TrafficRouting is enabled" // InvalidSetCanaryScaleTrafficPolicy indicates that TrafficRouting, required for SetCanaryScale, is missing InvalidSetCanaryScaleTrafficPolicy = "SetCanaryScale requires TrafficRouting to be set" // InvalidDurationMessage indicates the Duration value needs to be greater than 0 @@ -40,7 +42,7 @@ const ( InvalidStrategyMessage = "Multiple Strategies can not be listed" // DuplicatedServicesBlueGreenMessage the message to indicate that the rollout uses the same service for the active and preview services DuplicatedServicesBlueGreenMessage = "This rollout uses the same service for the active and preview services, but two different services are required." - // DuplicatedServicesMessage the message to indicate that the rollout uses the same service for the active and preview services + // DuplicatedServicesCanaryMessage indicates that the rollout uses the same service for the stable and canary services DuplicatedServicesCanaryMessage = "This rollout uses the same service for the stable and canary services, but two different services are required." // InvalidAntiAffinityStrategyMessage indicates that Anti-Affinity can only have one strategy listed InvalidAntiAffinityStrategyMessage = "AntiAffinity must have exactly one strategy listed" @@ -238,8 +240,17 @@ func ValidateRolloutStrategyCanary(rollout *v1alpha1.Rollout, fldPath *field.Pat if rollout.Spec.Strategy.Canary != nil && rollout.Spec.Strategy.Canary.TrafficRouting == nil && step.SetCanaryScale != nil { allErrs = append(allErrs, field.Invalid(stepFldPath.Child("setCanaryScale"), step.SetCanaryScale, InvalidSetCanaryScaleTrafficPolicy)) } - analysisRunArgs := []v1alpha1.AnalysisRunArgument{} + analysisRunArgs := make([]v1alpha1.AnalysisRunArgument, 0) if step.Experiment != nil { + for tmplIndex, template := range step.Experiment.Templates { + if template.Weight != nil { + if canary.TrafficRouting == nil { + allErrs = append(allErrs, field.Invalid(stepFldPath.Child("experiment").Child("templates").Index(tmplIndex).Child("weight"), *canary.Steps[i].Experiment.Templates[tmplIndex].Weight, InvalidCanaryExperimentTemplateWeightWithoutTrafficRouting)) + } else if canary.TrafficRouting.ALB == nil && canary.TrafficRouting.SMI == nil { + allErrs = append(allErrs, field.Invalid(stepFldPath.Child("experiment").Child("templates").Index(tmplIndex).Child("weight"), *canary.Steps[i].Experiment.Templates[tmplIndex].Weight, "Experiment template weight is only available for TrafficRouting with SMI and ALB at this time")) + } + } + } for _, analysis := range step.Experiment.Analyses { for _, arg := range analysis.Args { analysisRunArgs = append(analysisRunArgs, arg) diff --git a/pkg/apis/rollouts/validation/validation_test.go b/pkg/apis/rollouts/validation/validation_test.go index 99a164b850..704dd4d02c 100644 --- a/pkg/apis/rollouts/validation/validation_test.go +++ b/pkg/apis/rollouts/validation/validation_test.go @@ -365,3 +365,91 @@ func TestWorkloadRefWithTemplate(t *testing.T) { assert.Equal(t, 0, len(allErrs)) }) } + +func TestCanaryExperimentStepWithWeight(t *testing.T) { + canaryStrategy := &v1alpha1.CanaryStrategy{ + CanaryService: "canary", + StableService: "stable", + Steps: []v1alpha1.CanaryStep{{ + Experiment: &v1alpha1.RolloutExperimentStep{ + Templates: []v1alpha1.RolloutExperimentTemplate{{ + Name: "template", + Weight: pointer.Int32Ptr(20), + }}, + }, + }}, + } + ro := &v1alpha1.Rollout{} + ro.Spec.Strategy.Canary = canaryStrategy + + t.Run("invalid - no TrafficRouting set", func(t *testing.T) { + invalidRo := ro.DeepCopy() + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 1, len(allErrs)) + assert.Equal(t, "Experiment template weight cannot be set unless TrafficRouting is enabled", allErrs[0].Detail) + }) + + t.Run("invalid - empty TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{} + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 1, len(allErrs)) + assert.Equal(t, "Experiment template weight is only available for TrafficRouting with SMI and ALB at this time", allErrs[0].Detail) + }) + + t.Run("unsupported - Nginx TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + Nginx: &v1alpha1.NginxTrafficRouting{ + StableIngress: "nginx-ingress", + }, + } + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 1, len(allErrs)) + assert.Equal(t, "Experiment template weight is only available for TrafficRouting with SMI and ALB at this time", allErrs[0].Detail) + }) + + t.Run("unsupported - Ambassador TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + Ambassador: &v1alpha1.AmbassadorTrafficRouting{ + Mappings: []string{"stable-mapping"}, + }, + } + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 1, len(allErrs)) + assert.Equal(t, "Experiment template weight is only available for TrafficRouting with SMI and ALB at this time", allErrs[0].Detail) + }) + + t.Run("unsupported - Istio TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + Istio: &v1alpha1.IstioTrafficRouting{ + VirtualService: v1alpha1.IstioVirtualService{ + Name: "virtualSvc", + }, + }, + } + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 1, len(allErrs)) + assert.Equal(t, "Experiment template weight is only available for TrafficRouting with SMI and ALB at this time", allErrs[0].Detail) + }) + + t.Run("success - SMI TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + SMI: &v1alpha1.SMITrafficRouting{}, + } + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 0, len(allErrs)) + }) + + t.Run("success - ALB TrafficRouting", func(t *testing.T) { + invalidRo := ro.DeepCopy() + invalidRo.Spec.Strategy.Canary.TrafficRouting = &v1alpha1.RolloutTrafficRouting{ + ALB: &v1alpha1.ALBTrafficRouting{}, + } + allErrs := ValidateRolloutStrategyCanary(invalidRo, field.NewPath("")) + assert.Equal(t, 0, len(allErrs)) + }) +} diff --git a/rollout/mocks/TrafficRoutingReconciler.go b/rollout/mocks/TrafficRoutingReconciler.go index 12007d1436..32ce3f4a38 100644 --- a/rollout/mocks/TrafficRoutingReconciler.go +++ b/rollout/mocks/TrafficRoutingReconciler.go @@ -61,20 +61,27 @@ func (_m *TrafficRoutingReconciler) UpdateHash(canaryHash string, stableHash str return r0 } -// VerifyWeight provides a mock function with given fields: desiredWeight -func (_m *TrafficRoutingReconciler) VerifyWeight(desiredWeight int32) (bool, error) { - ret := _m.Called(desiredWeight) +// VerifyWeight provides a mock function with given fields: desiredWeight, additionalDestinations +func (_m *TrafficRoutingReconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { + _va := make([]interface{}, len(additionalDestinations)) + for _i := range additionalDestinations { + _va[_i] = additionalDestinations[_i] + } + var _ca []interface{} + _ca = append(_ca, desiredWeight) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 bool - if rf, ok := ret.Get(0).(func(int32) bool); ok { - r0 = rf(desiredWeight) + if rf, ok := ret.Get(0).(func(int32, ...trafficrouting.WeightDestination) bool); ok { + r0 = rf(desiredWeight, additionalDestinations...) } else { r0 = ret.Get(0).(bool) } var r1 error - if rf, ok := ret.Get(1).(func(int32) error); ok { - r1 = rf(desiredWeight) + if rf, ok := ret.Get(1).(func(int32, ...trafficrouting.WeightDestination) error); ok { + r1 = rf(desiredWeight, additionalDestinations...) } else { r1 = ret.Error(1) } diff --git a/rollout/trafficrouting.go b/rollout/trafficrouting.go index 4cffd358e5..945c81d1fe 100644 --- a/rollout/trafficrouting.go +++ b/rollout/trafficrouting.go @@ -151,7 +151,7 @@ func (c *rolloutContext) reconcileTrafficRouting() error { currentStep != nil && currentStep.SetWeight != nil if shouldVerifyWeight { - weightVerified, err := reconciler.VerifyWeight(desiredWeight) + weightVerified, err := reconciler.VerifyWeight(desiredWeight, weightDestinations...) if err != nil { return err } diff --git a/rollout/trafficrouting/alb/alb.go b/rollout/trafficrouting/alb/alb.go index 8f660c3fe9..80fa00fb1a 100644 --- a/rollout/trafficrouting/alb/alb.go +++ b/rollout/trafficrouting/alb/alb.go @@ -79,14 +79,13 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...tr actionService := r.cfg.Rollout.Spec.Strategy.Canary.StableService if r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService != "" { actionService = r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.RootService - } port := r.cfg.Rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort if !ingressutil.HasRuleWithService(ingress, actionService) { return fmt.Errorf("ingress does not have service `%s` in rules", actionService) } - desired, err := getDesiredAnnotations(ingress, rollout, port, desiredWeight) + desired, err := getDesiredAnnotations(ingress, rollout, port, desiredWeight, additionalDestinations...) if err != nil { return err } @@ -116,7 +115,7 @@ func (r *Reconciler) shouldVerifyWeight() bool { return defaults.VerifyTargetGroup() } -func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { if !r.shouldVerifyWeight() { return true, nil } @@ -127,11 +126,21 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { if err != nil { return false, err } + resourceIDToDest := map[string]trafficrouting.WeightDestination{} + canaryService := rollout.Spec.Strategy.Canary.CanaryService - resourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.Name, canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + canaryResourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.Name, canaryService, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + + for _, dest := range additionalDestinations { + resourceID := aws.BuildTargetGroupResourceID(rollout.Namespace, ingress.Name, dest.ServiceName, rollout.Spec.Strategy.Canary.TrafficRouting.ALB.ServicePort) + resourceIDToDest[resourceID] = dest + } + if len(ingress.Status.LoadBalancer.Ingress) == 0 { r.log.Infof("LoadBalancer not yet allocated") } + + numVerifiedWeights := 0 for _, lbIngress := range ingress.Status.LoadBalancer.Ingress { if lbIngress.Hostname == "" { continue @@ -152,22 +161,34 @@ func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { } logCtx := r.log.WithField("lb", *lb.LoadBalancerArn) for _, tg := range lbTargetGroups { - if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == resourceID { + if tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID] == canaryResourceID { if tg.Weight != nil { logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) - logCtx.Infof("canary weight of %s (desired: %d, current: %d)", resourceID, desiredWeight, *tg.Weight) + logCtx.Infof("canary weight of %s (desired: %d, current: %d)", canaryResourceID, desiredWeight, *tg.Weight) verified := *tg.Weight == desiredWeight if verified { + numVerifiedWeights += 1 r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight) } else { r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, canaryService, *tg.TargetGroupArn, desiredWeight, *tg.Weight) } - return verified, nil + } + } else if dest, ok := resourceIDToDest[tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID]]; ok { + if tg.Weight != nil { + logCtx := logCtx.WithField("tg", *tg.TargetGroupArn) + logCtx.Infof("%s weight of %s (desired: %d, current: %d)", dest.ServiceName, tg.Tags[aws.AWSLoadBalancerV2TagKeyResourceID], dest.Weight, *tg.Weight) + verified := *tg.Weight == dest.Weight + if verified { + numVerifiedWeights += 1 + r.cfg.Recorder.Eventf(rollout, record.EventOptions{EventReason: conditions.TargetGroupVerifiedReason}, conditions.TargetGroupVerifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight) + } else { + r.cfg.Recorder.Warnf(rollout, record.EventOptions{EventReason: conditions.TargetGroupUnverifiedReason}, conditions.TargetGroupUnverifiedWeightsMessage, dest.ServiceName, *tg.TargetGroupArn, dest.Weight, *tg.Weight) + } } } } } - return false, nil + return numVerifiedWeights == 1+len(additionalDestinations), nil } func calculatePatch(current *extensionsv1beta1.Ingress, desiredAnnotations map[string]string) ([]byte, bool, error) { @@ -185,34 +206,52 @@ func calculatePatch(current *extensionsv1beta1.Ingress, desiredAnnotations map[s }, extensionsv1beta1.Ingress{}) } -func getForwardActionString(r *v1alpha1.Rollout, port int32, desiredWeight int32) string { +func getForwardActionString(r *v1alpha1.Rollout, port int32, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) string { stableService := r.Spec.Strategy.Canary.StableService canaryService := r.Spec.Strategy.Canary.CanaryService portStr := strconv.Itoa(int(port)) + stableWeight := int32(100) + targetGroups := make([]ingressutil.ALBTargetGroup, 0) + // create target group for canary + targetGroups = append(targetGroups, ingressutil.ALBTargetGroup{ + ServiceName: canaryService, + ServicePort: portStr, + Weight: pointer.Int64Ptr(int64(desiredWeight)), + }) + // update stableWeight + stableWeight -= desiredWeight + + for _, dest := range additionalDestinations { + // Create target group for each additional destination + targetGroups = append(targetGroups, ingressutil.ALBTargetGroup{ + ServiceName: dest.ServiceName, + ServicePort: portStr, + Weight: pointer.Int64Ptr(int64(dest.Weight)), + }) + stableWeight -= dest.Weight + } + + // Create target group for stable with updated stableWeight + targetGroups = append(targetGroups, ingressutil.ALBTargetGroup{ + ServiceName: stableService, + ServicePort: portStr, + Weight: pointer.Int64Ptr(int64(stableWeight)), + }) + action := ingressutil.ALBAction{ Type: "forward", ForwardConfig: ingressutil.ALBForwardConfig{ - TargetGroups: []ingressutil.ALBTargetGroup{ - { - ServiceName: stableService, - ServicePort: portStr, - Weight: pointer.Int64Ptr(100 - int64(desiredWeight)), - }, { - ServiceName: canaryService, - ServicePort: portStr, - Weight: pointer.Int64Ptr(int64(desiredWeight)), - }, - }, + TargetGroups: targetGroups, }, } bytes := jsonutil.MustMarshal(action) return string(bytes) } -func getDesiredAnnotations(current *extensionsv1beta1.Ingress, r *v1alpha1.Rollout, port int32, desiredWeight int32) (map[string]string, error) { +func getDesiredAnnotations(current *extensionsv1beta1.Ingress, r *v1alpha1.Rollout, port int32, desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (map[string]string, error) { desired := current.DeepCopy().Annotations key := ingressutil.ALBActionAnnotationKey(r) - desired[key] = getForwardActionString(r, port, desiredWeight) + desired[key] = getForwardActionString(r, port, desiredWeight, additionalDestinations...) m, err := ingressutil.NewManagedALBActions(desired[ingressutil.ManagedActionsAnnotation]) if err != nil { return nil, err diff --git a/rollout/trafficrouting/alb/alb_test.go b/rollout/trafficrouting/alb/alb_test.go index c8146e6b45..dcede25813 100644 --- a/rollout/trafficrouting/alb/alb_test.go +++ b/rollout/trafficrouting/alb/alb_test.go @@ -6,6 +6,8 @@ import ( "fmt" "testing" + "github.com/argoproj/argo-rollouts/rollout/trafficrouting" + elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" @@ -67,13 +69,15 @@ const actionTemplate = `{ } }` +const actionTemplateWithExperiments = `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d}]}}` + func albActionAnnotation(stable string) string { return fmt.Sprintf("%s%s%s", ingressutil.ALBIngressAnnotation, ingressutil.ALBActionPrefix, stable) } func ingress(name string, stableSvc, canarySvc string, port, weight int32, managedBy string) *extensionsv1beta1.Ingress { managedByValue := fmt.Sprintf("%s:%s", managedBy, albActionAnnotation(stableSvc)) - action := fmt.Sprintf(actionTemplate, stableSvc, port, 100-weight, canarySvc, port, weight) + action := fmt.Sprintf(actionTemplate, canarySvc, port, weight, stableSvc, port, 100-weight) var a ingressutil.ALBAction err := json.Unmarshal([]byte(action), &a) if err != nil { @@ -373,3 +377,199 @@ func TestVerifyWeight(t *testing.T) { assert.True(t, weightVerified) } } + +func TestSetWeightWithMultipleBackends(t *testing.T) { + ro := fakeRollout("stable-svc", "canary-svc", "ingress", 443) + i := ingress("ingress", "stable-svc", "canary-svc", 443, 0, ro.Name) + client := fake.NewSimpleClientset(i) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressLister: k8sI.Extensions().V1beta1().Ingresses().Lister(), + }) + assert.NoError(t, err) + + weightDestinations := []trafficrouting.WeightDestination{ + { + ServiceName: "ex-svc-1", + PodTemplateHash: "", + Weight: 2, + }, + { + ServiceName: "ex-svc-2", + PodTemplateHash: "", + Weight: 3, + }, + } + err = r.SetWeight(10, weightDestinations...) + assert.Nil(t, err) + + actions := client.Actions() + assert.Len(t, client.Actions(), 1) + assert.Equal(t, "patch", actions[0].GetVerb()) + + patchedI := extensionsv1beta1.Ingress{} + err = json.Unmarshal(actions[0].(k8stesting.PatchAction).GetPatch(), &patchedI) + assert.Nil(t, err) + + servicePort := 443 + expectedAction := fmt.Sprintf(actionTemplateWithExperiments, "canary-svc", servicePort, 10, weightDestinations[0].ServiceName, servicePort, weightDestinations[0].Weight, weightDestinations[1].ServiceName, servicePort, weightDestinations[1].Weight, "stable-svc", servicePort, 85) + assert.Equal(t, expectedAction, patchedI.Annotations["alb.ingress.kubernetes.io/actions.stable-svc"]) + +} + +func TestVerifyWeightWithAdditionalDestinations(t *testing.T) { + weightDestinations := []trafficrouting.WeightDestination{ + { + ServiceName: "ex-svc-1", + PodTemplateHash: "", + Weight: 2, + }, + { + ServiceName: "ex-svc-2", + PodTemplateHash: "", + Weight: 3, + }, + } + newFakeReconciler := func() (*Reconciler, *fakeAWSClient) { + ro := fakeRollout("stable-svc", "canary-svc", "ingress", 443) + i := ingress("ingress", "stable-svc", "canary-svc", 443, 0, ro.Name) + 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) + + i.Status.LoadBalancer = corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + { + Hostname: "verify-weight-test-abc-123.us-west-2.elb.amazonaws.com", + }, + }, + } + + client := fake.NewSimpleClientset(i) + k8sI := kubeinformers.NewSharedInformerFactory(client, 0) + k8sI.Extensions().V1beta1().Ingresses().Informer().GetIndexer().Add(i) + r, err := NewReconciler(ReconcilerConfig{ + Rollout: ro, + Client: client, + Recorder: record.NewFakeEventRecorder(), + ControllerKind: schema.GroupVersionKind{Group: "foo", Version: "v1", Kind: "Bar"}, + IngressLister: k8sI.Extensions().V1beta1().Ingresses().Lister(), + VerifyWeight: pointer.BoolPtr(true), + }) + assert.NoError(t, err) + fakeAWS := fakeAWSClient{} + r.aws = &fakeAWS + return r, &fakeAWS + } + + // LoadBalancer found, but experiment weights not present + { + r, fakeClient := newFakeReconciler() + fakeClient.loadBalancer = &elbv2types.LoadBalancer{ + LoadBalancerArn: pointer.StringPtr("lb-abc123"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.False(t, weightVerified) + } + + // LoadBalancer found, with incorrect weights + { + r, fakeClient := newFakeReconciler() + fakeClient.loadBalancer = &elbv2types.LoadBalancer{ + LoadBalancerArn: pointer.StringPtr("lb-abc123"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: pointer.Int32Ptr(100), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-1:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: pointer.Int32Ptr(100), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-2:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.False(t, weightVerified) + } + + // LoadBalancer found, with all correct weights + { + r, fakeClient := newFakeReconciler() + fakeClient.loadBalancer = &elbv2types.LoadBalancer{ + LoadBalancerArn: pointer.StringPtr("lb-abc123"), + DNSName: pointer.StringPtr("verify-weight-test-abc-123.us-west-2.elb.amazonaws.com"), + } + fakeClient.targetGroups = []aws.TargetGroupMeta{ + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: pointer.Int32Ptr(10), + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-canary-svc:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: &weightDestinations[0].Weight, + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-1:443", + }, + }, + { + TargetGroup: elbv2types.TargetGroup{ + TargetGroupArn: pointer.StringPtr("tg-abc123"), + }, + Weight: &weightDestinations[1].Weight, + Tags: map[string]string{ + aws.AWSLoadBalancerV2TagKeyResourceID: "default/ingress-ex-svc-2:443", + }, + }, + } + + weightVerified, err := r.VerifyWeight(10, weightDestinations...) + assert.NoError(t, err) + assert.True(t, weightVerified) + } +} diff --git a/rollout/trafficrouting/ambassador/ambassador.go b/rollout/trafficrouting/ambassador/ambassador.go index e520a18c57..30a89cd3b4 100644 --- a/rollout/trafficrouting/ambassador/ambassador.go +++ b/rollout/trafficrouting/ambassador/ambassador.go @@ -251,7 +251,7 @@ func buildCanaryService(baseMapping *unstructured.Unstructured, canarySvc string return fmt.Sprintf("%s:%s", canarySvc, port) } -func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { return true, nil } diff --git a/rollout/trafficrouting/istio/istio.go b/rollout/trafficrouting/istio/istio.go index 5b7204bd84..925eb01b3d 100644 --- a/rollout/trafficrouting/istio/istio.go +++ b/rollout/trafficrouting/istio/istio.go @@ -559,7 +559,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...tr return err } -func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { return true, nil } diff --git a/rollout/trafficrouting/nginx/nginx.go b/rollout/trafficrouting/nginx/nginx.go index 5054004b96..9bfa2314c7 100644 --- a/rollout/trafficrouting/nginx/nginx.go +++ b/rollout/trafficrouting/nginx/nginx.go @@ -231,7 +231,7 @@ func (r *Reconciler) SetWeight(desiredWeight int32, additionalDestinations ...tr return nil } -func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { return true, nil } diff --git a/rollout/trafficrouting/smi/smi.go b/rollout/trafficrouting/smi/smi.go index 6cf372546f..0075061570 100644 --- a/rollout/trafficrouting/smi/smi.go +++ b/rollout/trafficrouting/smi/smi.go @@ -175,7 +175,7 @@ func NewReconciler(cfg ReconcilerConfig) (*Reconciler, error) { return r, nil } -func (r *Reconciler) VerifyWeight(desiredWeight int32) (bool, error) { +func (r *Reconciler) VerifyWeight(desiredWeight int32, additionalDestinations ...trafficrouting.WeightDestination) (bool, error) { return true, nil } diff --git a/rollout/trafficrouting/trafficroutingutil.go b/rollout/trafficrouting/trafficroutingutil.go index e9b609314e..d9d34e208a 100644 --- a/rollout/trafficrouting/trafficroutingutil.go +++ b/rollout/trafficrouting/trafficroutingutil.go @@ -6,8 +6,8 @@ type TrafficRoutingReconciler interface { UpdateHash(canaryHash, stableHash string) error // SetWeight sets the canary weight to the desired weight SetWeight(desiredWeight int32, additionalDestinations ...WeightDestination) error - // VerifyWeight returns true if the canary is at the desired weight - VerifyWeight(desiredWeight int32) (bool, error) + // VerifyWeight returns true if the canary is at the desired weight and additonalDestinations are at the weights specified + VerifyWeight(desiredWeight int32, additionalDestinations ...WeightDestination) (bool, 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 e5214ccf99..95ae52e562 100644 --- a/rollout/trafficrouting_test.go +++ b/rollout/trafficrouting_test.go @@ -308,7 +308,8 @@ func TestRolloutUsePreviousSetWeight(t *testing.T) { assert.Equal(t, int32(10), desiredWeight) return nil }) - f.fakeTrafficRouting.On("VerifyWeight", mock.Anything).Return(true, nil) + f.fakeTrafficRouting.On("VerifyWeight", mock.Anything, mock.Anything).Return(true, nil) + f.fakeTrafficRouting.On("error patching alb ingress", mock.Anything, mock.Anything).Return(true, nil) f.run(getKey(r2, t)) } diff --git a/test/e2e/alb/rollout-alb-experiment.yaml b/test/e2e/alb/rollout-alb-experiment.yaml new file mode 100644 index 0000000000..e7b975701c --- /dev/null +++ b/test/e2e/alb/rollout-alb-experiment.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-root +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-canary +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: v1 +kind: Service +metadata: + name: alb-rollout-stable +spec: + type: NodePort + ports: + - port: 80 + targetPort: http + protocol: TCP + name: http + selector: + app: alb-rollout +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: alb-rollout-ingress + annotations: + kubernetes.io/ingress.class: alb +spec: + rules: + - http: + paths: + - path: /* + backend: + serviceName: alb-rollout-root + servicePort: use-annotation +--- +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: alb-rollout +spec: + selector: + matchLabels: + app: alb-rollout + template: + metadata: + labels: + app: alb-rollout + spec: + containers: + - name: alb-rollout + image: nginx:1.19-alpine + ports: + - name: http + containerPort: 80 + protocol: TCP + resources: + requests: + memory: 16Mi + cpu: 5m + strategy: + canary: + canaryService: alb-rollout-canary + stableService: alb-rollout-stable + trafficRouting: + alb: + ingress: alb-rollout-ingress + rootService: alb-rollout-root + servicePort: 80 + steps: + - setWeight: 10 + - experiment: + templates: + - name: experiment-alb + specRef: canary + weight: 20 diff --git a/test/e2e/aws_test.go b/test/e2e/aws_test.go index 4190449aef..1501e0b289 100644 --- a/test/e2e/aws_test.go +++ b/test/e2e/aws_test.go @@ -3,10 +3,13 @@ package e2e import ( + "fmt" "os" "testing" + "time" "github.com/stretchr/testify/suite" + "github.com/tj/assert" "github.com/argoproj/argo-rollouts/test/fixtures" ) @@ -19,6 +22,11 @@ func TestAWSSuite(t *testing.T) { suite.Run(t, new(AWSSuite)) } +const actionTemplate = `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d}]}}` + +const actionTemplateWithExperiment = `{"Type":"forward","ForwardConfig":{"TargetGroups":[{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d},{"ServiceName":"%s","ServicePort":"%d","Weight":%d}]}}` + + // TestALBUpdate is a simple integration test which verifies the controller can work in a real AWS // environment. It is intended to be run with the `--aws-verify-target-group` controller flag. Success of // this test against a controller using that flag, indicates that the controller was able to perform @@ -46,3 +54,54 @@ func (s *AWSSuite) TestALBBlueGreenUpdate() { UpdateSpec(). WaitForRolloutStatus("Healthy") } + +func (s *AWSSuite) TestALBExperimentStep() { + s.Given(). + RolloutObjects("@alb/rollout-alb-experiment.yaml"). + When(). + ApplyManifests(). + WaitForRolloutStatus("Healthy"). + Then(). + Assert(func(t *fixtures.Then) { + ingress := t.GetALBIngress() + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + port := 80 + expectedAction := fmt.Sprintf(actionTemplate, "alb-rollout-canary", port, 0, "alb-rollout-stable", port, 100) + assert.Equal(s.T(), expectedAction, action) + }). + ExpectExperimentCount(0). + When(). + UpdateSpec(). + WaitForRolloutCanaryStepIndex(1). + Sleep(10*time.Second). + Then(). + Assert(func(t *fixtures.Then) { + ingress := t.GetALBIngress() + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + ex := t.GetRolloutExperiments().Items[0] + exServiceName := ex.Status.TemplateStatuses[0].ServiceName + + port := 80 + expectedAction := fmt.Sprintf(actionTemplateWithExperiment, "alb-rollout-canary", port, 10, exServiceName, port, 20, "alb-rollout-stable", port, 70) + assert.Equal(s.T(), expectedAction, action) + }). + When(). + PromoteRollout(). + WaitForRolloutStatus("Healthy"). + Sleep(1*time.Second). // stable is currently set first, and then changes made to VirtualServices/DestinationRules + Then(). + Assert(func(t *fixtures.Then) { + ingress := t.GetALBIngress() + action, ok := ingress.Annotations["alb.ingress.kubernetes.io/actions.alb-rollout-root"] + assert.True(s.T(), ok) + + port := 80 + expectedAction := fmt.Sprintf(actionTemplate, "alb-rollout-canary", port, 0, "alb-rollout-stable", port, 100) + assert.Equal(s.T(), expectedAction, action) + }) +} + diff --git a/test/fixtures/common.go b/test/fixtures/common.go index 77a48b0d95..b723882928 100644 --- a/test/fixtures/common.go +++ b/test/fixtures/common.go @@ -13,16 +13,15 @@ import ( "text/tabwriter" "time" + "github.com/ghodss/yaml" smiv1alpha1 "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/split/v1alpha1" - smiclientset "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned" - - "github.com/ghodss/yaml" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" @@ -483,6 +482,14 @@ func (c *Common) GetServices() (*corev1.Service, *corev1.Service) { return desiredSvc, stableSvc } +func (c *Common) GetALBIngress() *extensionsv1beta1.Ingress { + ro := c.Rollout() + name := ro.Spec.Strategy.Canary.TrafficRouting.ALB.Ingress + ingress, err := c.kubeClient.ExtensionsV1beta1().Ingresses(c.namespace).Get(c.Context, name, metav1.GetOptions{}) + c.CheckError(err) + return ingress +} + func (c *Common) GetTrafficSplit() *smiv1alpha1.TrafficSplit { ro := c.Rollout() name := ro.Spec.Strategy.Canary.TrafficRouting.SMI.TrafficSplitName