From 22f1bb9dc89f4aeb8e585b10ca9f03be1ddc0016 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Tue, 7 Dec 2021 15:23:52 +0200 Subject: [PATCH 01/15] add initial implementation for cloud output support --- api/v1alpha1/k6_types.go | 2 +- config/crd/bases/k6.io_k6s.yaml | 2 + config/default/kustomization.yaml | 11 ++ config/rbac/role.yaml | 9 + controllers/k6_controller.go | 8 + controllers/k6_create.go | 4 +- controllers/k6_finish.go | 29 ++- controllers/k6_initialize.go | 203 +++++++++++++++++++ controllers/k6_start.go | 8 +- go.mod | 51 +++-- go.sum | 311 ++++++++++++++++++++++++++++-- pkg/cloud/cloud.go | 50 +++++ pkg/resources/containers/curl.go | 1 + pkg/resources/jobs/initializer.go | 109 +++++++++++ pkg/resources/jobs/runner.go | 129 +++---------- pkg/types/types.go | 165 ++++++++++++++++ pkg/types/types_test.go | 80 ++++++++ 17 files changed, 1021 insertions(+), 151 deletions(-) create mode 100644 controllers/k6_initialize.go create mode 100644 pkg/cloud/cloud.go create mode 100644 pkg/resources/jobs/initializer.go create mode 100644 pkg/types/types.go create mode 100644 pkg/types/types_test.go diff --git a/api/v1alpha1/k6_types.go b/api/v1alpha1/k6_types.go index 8d901cf5..fdc31a01 100644 --- a/api/v1alpha1/k6_types.go +++ b/api/v1alpha1/k6_types.go @@ -90,7 +90,7 @@ type K6Configmap struct { type Cleanup string // Stage describes which stage of the test execution lifecycle our runners are in -// +kubebuilder:validation:Enum=created;started;finished +// +kubebuilder:validation:Enum=initialization;initialized;created;started;finished type Stage string // K6Status defines the observed state of K6 diff --git a/config/crd/bases/k6.io_k6s.yaml b/config/crd/bases/k6.io_k6s.yaml index 0ce4aa8f..de4b2b81 100644 --- a/config/crd/bases/k6.io_k6s.yaml +++ b/config/crd/bases/k6.io_k6s.yaml @@ -1956,6 +1956,8 @@ spec: description: Stage describes which stage of the test execution lifecycle our runners are in enum: + - initialization + - initialized - created - started - finished diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 254418f6..0b48ca42 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -62,3 +62,14 @@ vars: # kind: Service # version: v1 # name: webhook-service + +# Uncomment this section if you need cloud output and copy-paste your token +# secretGenerator: +# - name: cloud-token +# literals: +# - token= +# options: +# annotations: +# kubernetes.io/service-account.name: k6-operator-controller +# labels: +# k6cloud: token diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 9ad24385..1be289c2 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -45,6 +45,7 @@ rules: - "" resources: - pods + - pods/log verbs: - get - list @@ -70,3 +71,11 @@ rules: - get - patch - update + - apiGroups: + - "" + resources: + - secrets + verbs: + - list + - get + - watch diff --git a/controllers/k6_controller.go b/controllers/k6_controller.go index d6641550..d171fa69 100644 --- a/controllers/k6_controller.go +++ b/controllers/k6_controller.go @@ -56,12 +56,20 @@ func (r *K6Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{Requeue: true}, err } + log.Info(fmt.Sprintf("Reconcile(); stage = %s", k6.Status.Stage)) + switch k6.Status.Stage { case "": + return InitializeJobs(ctx, log, k6, r) + case "initialization": + // here we're just waiting until initialize is done + return ctrl.Result{}, nil + case "initialized": return CreateJobs(ctx, log, k6, r) case "created": return StartJobs(ctx, log, k6, r) case "started": + // wait for test to finish and then mark as finished return FinishJobs(ctx, log, k6, r) case "finished": // delete if configured diff --git a/controllers/k6_create.go b/controllers/k6_create.go index 957085c2..0157dd22 100644 --- a/controllers/k6_create.go +++ b/controllers/k6_create.go @@ -14,7 +14,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ) -// CreateJobs that will spawn k6 pods, running distributed tests +// CreateJobs creates jobs that will spawn k6 pods for distributed test func CreateJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (ctrl.Result, error) { var err error var res ctrl.Result @@ -62,7 +62,7 @@ func launchTest(ctx context.Context, k6 *v1alpha1.K6, index int, log logr.Logger msg := fmt.Sprintf("Launching k6 test #%d", index) log.Info(msg) - if job, err = jobs.NewRunnerJob(k6, index); err != nil { + if job, err = jobs.NewRunnerJob(k6, index, testRunId, token); err != nil { log.Error(err, "Failed to generate k6 test job") return err } diff --git a/controllers/k6_finish.go b/controllers/k6_finish.go index b7fb9550..4378405e 100644 --- a/controllers/k6_finish.go +++ b/controllers/k6_finish.go @@ -6,17 +6,22 @@ import ( "github.com/go-logr/logr" "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/cloud" + "github.com/grafana/k6-operator/pkg/types" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/labels" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) -// Mark k6 as finished as jobs finish +// FinishJobs waits for the pods to finish, performs finishing call for cloud output and moves state to "finished". func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (ctrl.Result, error) { + log.Info("Waiting for pods to finish") + selector := labels.SelectorFromSet(map[string]string{ - "app": "k6", - "k6_cr": k6.Name, + "app": "k6", + "k6_cr": k6.Name, + "runner": "true", }) opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} @@ -27,7 +32,7 @@ func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco return ctrl.Result{}, err } - //TODO: We should distinguish between suceeded/failed + //TODO: We should distinguish between Suceeded/Failed/Unknown var finished int32 for _, job := range jl.Items { if job.Status.Active != 0 { @@ -36,15 +41,23 @@ func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco finished++ } - log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.Spec.Parallelism+1)) + log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.Spec.Parallelism)) - // parallelism (pods) + starter pod = total expected - if finished == k6.Spec.Parallelism+1 { + if finished >= k6.Spec.Parallelism { k6.Status.Stage = "finished" if err := r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource") + return ctrl.Result{}, err } - } + if cli := types.ParseCLI(&k6.Spec); cli.HasCloudOut { + if err := cloud.FinishTestRun(testRunId); err != nil { + log.Error(err, "Could not finish test run with cloud output") + return ctrl.Result{}, err + } + } + + log.Info(fmt.Sprintf("Cloud test run %s was finished", testRunId)) + } return ctrl.Result{}, nil } diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go new file mode 100644 index 00000000..4928bc8e --- /dev/null +++ b/controllers/k6_initialize.go @@ -0,0 +1,203 @@ +package controllers + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "time" + + "github.com/go-logr/logr" + "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/cloud" + "github.com/grafana/k6-operator/pkg/resources/jobs" + "github.com/grafana/k6-operator/pkg/types" + k6types "go.k6.io/k6/lib/types" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// k6 Cloud related vars +// Right now operator works with one test at a time so these should be safe. +var ( + testRunId string + token string +) + +// InitializeJobs creates jobs that will run initial checks for distributed test if any are necessary +func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { + log.Info("Initialize test") + + k6.Status.Stage = "initialization" + if err = r.Client.Status().Update(ctx, k6); err != nil { + log.Error(err, "Could not update status of custom resource") + return + } + + if cli := types.ParseCLI(&k6.Spec); cli.HasCloudOut { + var ( + secrets corev1.SecretList + secretOpts = &client.ListOptions{ + // TODO: find out a better way to get namespace here + Namespace: "k6-operator-system", + LabelSelector: labels.SelectorFromSet(map[string]string{ + "k6cloud": "token", + }), + } + ) + if err := r.List(ctx, &secrets, secretOpts); err != nil { + log.Error(err, "Failed to load k6 Cloud token") + return res, err + } + + if len(secrets.Items) < 1 { + err := fmt.Errorf("There are no secrets to hold k6 Cloud token") + log.Error(err, err.Error()) + return res, err + } + + if t, ok := secrets.Items[0].Data["token"]; !ok { + err := fmt.Errorf("The secret doesn't have a field token for k6 Cloud") + log.Error(err, err.Error()) + return res, err + } else { + token = string(t) + } + log.Info("Token for k6 Cloud was loaded.") + + var initializer *batchv1.Job + if initializer, err = jobs.NewInitializerJob(k6, cli.ArchiveArgs); err != nil { + return res, err + } + + log.Info(fmt.Sprintf("Initializer job is ready to start with image `%s` and command `%s`", + initializer.Spec.Template.Spec.Containers[0].Image, initializer.Spec.Template.Spec.Containers[0].Command)) + + if err = ctrl.SetControllerReference(k6, initializer, r.Scheme); err != nil { + log.Error(err, "Failed to set controller reference for the initialize job") + return + } + + if err = r.Create(ctx, initializer); err != nil { + log.Error(err, "Failed to launch k6 test initializer") + return + } + err = wait.PollImmediate(time.Second*5, time.Second*60, func() (done bool, err error) { + var ( + listOpts = &client.ListOptions{ + Namespace: k6.Namespace, + LabelSelector: labels.SelectorFromSet(map[string]string{ + "app": "k6", + "k6_cr": k6.Name, + "job-name": fmt.Sprintf("%s-initializer", k6.Name), + }), + } + podList = &corev1.PodList{} + ) + if err := r.List(ctx, podList, listOpts); err != nil { + log.Error(err, "Could not list pods") + return false, err + } + if len(podList.Items) < 1 { + log.Info("No initializing pod found yet") + return false, nil + } + + // there should be only 1 initializer pod + if podList.Items[0].Status.Phase != "Succeeded" { + log.Info("Waiting for initializing pod to finish") + return false, nil + } + + // Here we need to get the output of the pod + // pods/log is not currently supported by controller-runtime client and it is officially + // recommended to use REST client instead: + // https://github.com/kubernetes-sigs/controller-runtime/issues/1229 + + var opts corev1.PodLogOptions + config, err := rest.InClusterConfig() + if err != nil { + log.Error(err, "unable to fetch in-cluster REST config") + // don't return here + return false, nil + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Error(err, "unable to get access to clientset") + // don't return here + return false, nil + } + req := clientset.CoreV1().Pods(k6.Namespace).GetLogs(podList.Items[0].Name, &opts) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) + defer cancel() + + podLogs, err := req.Stream(ctx) + if err != nil { + log.Error(err, "unable to stream logs from the pod") + // don't return here + return false, nil + } + defer podLogs.Close() + + buf := new(bytes.Buffer) + _, err = io.Copy(buf, podLogs) + if err != nil { + log.Error(err, "unable to copy logs from the pod") + return false, err + } + + var execSpec struct { + TotalDuration k6types.NullDuration `json:"totalDuration"` + MaxVUs uint64 `json:"maxVUs"` + } + + if err := json.Unmarshal(buf.Bytes(), &execSpec); err != nil { + return true, err + } + + log.Info(fmt.Sprintf("Execution requirements: %+v", execSpec)) + + if int32(execSpec.MaxVUs) < k6.Spec.Parallelism { + err = fmt.Errorf("number of instances > number of VUs") + // TODO maybe change this to a warning and simply set parallelism = maxVUs and proceed with execution? + // But logr doesn't seem to have warning level by default, only with V() method... + // It makes sense to return to this after / during logr VS logrus issue https://github.com/grafana/k6-operator/issues/84 + log.Error(err, "Parallelism argument cannot be larger than maximum VUs in the script", + "maxVUs", execSpec.MaxVUs, + "parallelism", k6.Spec.Parallelism) + return false, err + } + + if refID, err := cloud.CreateTestRun("k6-operator-test", token, + execSpec.MaxVUs, execSpec.TotalDuration.TimeDuration().Seconds(), + log); err != nil { + return true, err + } else { + testRunId = refID + log.Info(fmt.Sprintf("Created cloud test run: %s", testRunId)) + return true, nil + } + }) + } + + if err != nil { + log.Error(err, "Failed to initialize script with cloud output") + return + } + + k6.Status.Stage = "initialized" + if err = r.Client.Status().Update(ctx, k6); err != nil { + log.Error(err, "Could not update status of custom resource") + return + } + + return res, nil +} diff --git a/controllers/k6_start.go b/controllers/k6_start.go index 358c7ee3..10af9b7a 100644 --- a/controllers/k6_start.go +++ b/controllers/k6_start.go @@ -33,13 +33,13 @@ func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Recon err := wait.PollImmediate(time.Second*5, time.Second*60, func() (done bool, err error) { selector := labels.SelectorFromSet(map[string]string{ - "app": "k6", - "k6_cr": k6.Name, + "app": "k6", + "k6_cr": k6.Name, + "runner": "true", }) opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} pl := &v1.PodList{} - if e := r.List(ctx, pl, opts); e != nil { log.Error(e, "Could not list pods") return false, e @@ -80,7 +80,7 @@ func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Recon starter := jobs.NewStarterJob(k6, hostnames) if err = ctrl.SetControllerReference(k6, starter, r.Scheme); err != nil { - log.Error(err, "Failed to set controller reference for job") + log.Error(err, "Failed to set controller reference for the start job") } if err = r.Create(ctx, starter); err != nil { diff --git a/go.mod b/go.mod index c568ae27..6e30bf41 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.17 require ( github.com/go-logr/logr v0.1.0 github.com/go-test/deep v1.0.7 - github.com/onsi/ginkgo v1.12.1 + github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 + github.com/sirupsen/logrus v1.8.1 + github.com/stretchr/testify v1.7.0 + go.k6.io/k6 v0.35.0 k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 @@ -14,48 +17,66 @@ require ( ) require ( - cloud.google.com/go v0.38.0 // indirect + cloud.google.com/go v0.46.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect + github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06 // indirect github.com/evanphx/json-patch v4.5.0+incompatible // indirect + github.com/fatih/color v1.12.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/zapr v0.1.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect - github.com/golang/protobuf v1.4.2 // indirect - github.com/google/go-cmp v0.4.0 // indirect + github.com/golang/protobuf v1.4.3 // indirect + github.com/google/go-cmp v0.5.1 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/uuid v1.1.1 // indirect + github.com/google/uuid v1.1.2 // indirect github.com/googleapis/gnostic v0.3.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/imdario/mergo v0.3.9 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.10 // indirect + github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/kubernetes/helm v2.9.0+incompatible // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/nxadm/tail v1.4.4 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect github.com/pkg/errors v0.8.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.4.1 // indirect github.com/prometheus/procfs v0.0.11 // indirect + github.com/spf13/afero v1.2.2 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 // indirect - golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect - golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 // indirect - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect - golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect - golang.org/x/text v0.3.3 // indirect - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect + golang.org/x/net v0.0.0-20211101194204-95aca89e93de // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect + golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect + golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect + golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gomodules.xyz/jsonpatch/v2 v2.0.1 // indirect - google.golang.org/appengine v1.5.0 // indirect - google.golang.org/protobuf v1.23.0 // indirect + google.golang.org/appengine v1.6.1 // indirect + google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12 // indirect + gopkg.in/guregu/null.v3 v3.3.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect k8s.io/apiextensions-apiserver v0.18.6 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 34a63f82..de6b77c5 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,17 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= @@ -10,18 +20,31 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v0.0.0-20180330214955-e67964b4021a/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Soontao/goHttpDigestClient v0.0.0-20170320082612-6d28bb1415c5/go.mod h1:5Q4+CyR7+Q3VMG8f78ou+QSX/BNUNUx5W48eFRat8DQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -29,11 +52,19 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -42,37 +73,56 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06 h1:XqC5eocqw7r3+HOhKYqaYH07XBiBDp9WE3NQK8XHSn4= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813/go.mod h1:P+oSoE9yhSRvsmYyZsshflcR6ePWYLql6UU1amW13IM= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= @@ -120,6 +170,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= @@ -133,86 +185,152 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9 github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20190402204710-8ff2fc3824fc/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jhump/protoreflect v1.10.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes/helm v2.9.0+incompatible h1:X6Tl40RMiqT0GD8hT3+jFPgkZV1EB6vYsoJDX8YkY+c= +github.com/kubernetes/helm v2.9.0+incompatible/go.mod h1:3Nb8I82ptmDi7OvvBQK25X1bwxg+WMAkusUQXHxu8ag= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manyminds/api2go v0.0.0-20180125085803-95be7bd0455e/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa h1:lx8ZnNPwjkXSzOROz0cg69RlErRXs+L3eDkggASWKLo= +github.com/mccutchen/go-httpbin v1.1.2-0.20190116014521-c5cb2f4802fa/go.mod h1:fhpOYavp5g2K74XDl/ao2y4KvhqVtKlkg1e+0UaQv7I= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -224,19 +342,26 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -245,8 +370,10 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -254,23 +381,41 @@ github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -278,26 +423,41 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.k6.io/k6 v0.35.0 h1:nvUSaYEngrh05OxcUiIsB0B+VQsg73OKBtvObHPBabs= +go.k6.io/k6 v0.35.0/go.mod h1:ges4SvRBSi9xMOGmIlv7l/7zwuAE+WiHN+9MlP4SxgE= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -306,79 +466,134 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI= +golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211101194204-95aca89e93de h1:dKoXPECQZ51dGVSkuiD9YzeNpLT4UPUY4d3xo0sWrkU= +golang.org/x/net v0.0.0-20211101194204-95aca89e93de/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= +golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f h1:yQJrRE0hDxDFmZLlRaw+3vusO4fwNHgHIjUOMO7bHYI= +golang.org/x/text v0.3.7-0.20210503195748-5c7c50ebbd4f/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -387,45 +602,95 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200903010400-9bfcb5116336 h1:ZcAny/XH59BbzUOKydQpvIlklwibW3T9SvDE5cGhdzc= +google.golang.org/genproto v0.0.0-20200903010400-9bfcb5116336/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12 h1:OwhZOOMuf7leLaSCuxtQ9FW7ui2L2L6UKOtKAUqovUQ= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/guregu/null.v2 v2.1.2/go.mod h1:XORrx8tyS5ZDcyUboCIxQtta/Aujk/6pfWrn9Xe33mU= +gopkg.in/guregu/null.v3 v3.3.0 h1:8j3ggqq+NgKt/O7mbFVUFKUMWN+l1AmT5jQmJ6nPh2c= +gopkg.in/guregu/null.v3 v3.3.0/go.mod h1:E4tX2Qe3h7QdL+uZ3a0vqvYwKQsRSQKM5V4YltdgH9Y= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -434,14 +699,21 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= @@ -466,6 +738,7 @@ k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA= sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E= diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go new file mode 100644 index 00000000..f4729306 --- /dev/null +++ b/pkg/cloud/cloud.go @@ -0,0 +1,50 @@ +package cloud + +import ( + "os" + "time" + + "github.com/go-logr/logr" + "github.com/sirupsen/logrus" + "go.k6.io/k6/cloudapi" + "go.k6.io/k6/lib" + "go.k6.io/k6/lib/consts" +) + +var client *cloudapi.Client + +func CreateTestRun(name string, token string, vus uint64, duration float64, log logr.Logger) (string, error) { + cloudConfig := cloudapi.NewConfig() + projectId := cloudConfig.ProjectID.ValueOrZero() + + logger := &logrus.Logger{ + Out: os.Stdout, + Formatter: new(logrus.TextFormatter), + Hooks: make(logrus.LevelHooks), + Level: logrus.InfoLevel, + } + + client = cloudapi.NewClient(logger, token, cloudConfig.Host.String, consts.Version, time.Duration(time.Minute)) + resp, err := client.CreateTestRun(&cloudapi.TestRun{ + Name: name, + ProjectID: projectId, + VUsMax: int64(vus), + Thresholds: map[string][]string{}, + // This is heuristic increase of duration to take into account that it takes time to start the pods. + // By current observations, it shouldn't matter that much since we're sending a finish call in the end, + // but it would be good to come up with another solution. + Duration: int64(duration) * 2, + }) + + if err != nil { + return "", err + } + + return resp.ReferenceID, nil +} + +func FinishTestRun(refID string) error { + return client.TestFinished(refID, cloudapi.ThresholdResult( + map[string]map[string]bool{}, + ), false, lib.RunStatusFinished) +} diff --git a/pkg/resources/containers/curl.go b/pkg/resources/containers/curl.go index d8cdd157..f8e8bd4c 100644 --- a/pkg/resources/containers/curl.go +++ b/pkg/resources/containers/curl.go @@ -27,6 +27,7 @@ func NewCurlContainer(hostnames []string, image string, command []string, env [] for _, hostname := range hostnames { parts = append(parts, fmt.Sprintf("curl --retry 3 -X PATCH -H 'Content-Type: application/json' http://%s:6565/v1/status -d '%s'", hostname, req)) } + return corev1.Container{ Name: "k6-curl", Image: image, diff --git a/pkg/resources/jobs/initializer.go b/pkg/resources/jobs/initializer.go new file mode 100644 index 00000000..7dc4e25f --- /dev/null +++ b/pkg/resources/jobs/initializer.go @@ -0,0 +1,109 @@ +package jobs + +import ( + "fmt" + "strconv" + + "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/types" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NewInitializerJob builds a template used to initializefor creating a starter job +func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { + script, err := types.ParseScript(&k6.Spec) + if err != nil { + return nil, err + } + + var ( + image = "ghcr.io/grafana/operator:latest-runner" + annotations = make(map[string]string) + labels = newLabels(k6.Name) + serviceAccountName = "default" + automountServiceAccountToken = true + ports = append([]corev1.ContainerPort{{ContainerPort: 6565}}, k6.Spec.Ports...) + ) + + if k6.Spec.Runner.Image != "" { + image = k6.Spec.Runner.Image + } + + if k6.Spec.Runner.Metadata.Annotations != nil { + annotations = k6.Spec.Runner.Metadata.Annotations + } + + if k6.Spec.Runner.Metadata.Labels != nil { + for k, v := range k6.Spec.Runner.Metadata.Labels { // Order not specified + if _, ok := labels[k]; !ok { + labels[k] = v + } + } + } + + if k6.Spec.Runner.ServiceAccountName != "" { + serviceAccountName = k6.Spec.Runner.ServiceAccountName + } + + if k6.Spec.Runner.AutomountServiceAccountToken != "" { + automountServiceAccountToken, _ = strconv.ParseBool(k6.Spec.Runner.AutomountServiceAccountToken) + } + + var ( + // k6 allows to run archive command on archives too so type of file here doesn't matter + scriptName = fmt.Sprintf("/test/%s", script.File) + archiveName = fmt.Sprintf("./%s.archived.tar", script.File) + ) + command, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, []string{"sh", "-c"}) + command = append(command, fmt.Sprintf("k6 archive %s -O %s %s && k6 inspect --execution-requirements %s", + scriptName, archiveName, argLine, + archiveName)) + + env := append(newIstioEnvVar(k6.Spec.Scuttle, istioEnabled), k6.Spec.Runner.Env...) + + return &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-initializer", k6.Name), + Namespace: k6.Namespace, + Labels: labels, + Annotations: annotations, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: annotations, + }, + Spec: corev1.PodSpec{ + AutomountServiceAccountToken: &automountServiceAccountToken, + ServiceAccountName: serviceAccountName, + Affinity: k6.Spec.Runner.Affinity, + NodeSelector: k6.Spec.Runner.NodeSelector, + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + ImagePullPolicy: "Never", + Image: image, + Name: "k6", + Command: command, + Env: env, + Resources: k6.Spec.Runner.Resources, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "k6-test-volume", + MountPath: "/test", + }, + }, + Ports: ports, + }, + }, + Volumes: []corev1.Volume{ + script.Volume(), + }, + }, + }, + }, + }, nil +} diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index c2da07c3..a807d14f 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -1,7 +1,6 @@ package jobs import ( - "errors" "fmt" "strconv" @@ -9,20 +8,14 @@ import ( "github.com/grafana/k6-operator/api/v1alpha1" "github.com/grafana/k6-operator/pkg/segmentation" + "github.com/grafana/k6-operator/pkg/types" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Internal script type created from Spec.script possible options -type Script struct { - Name string - File string - Type string -} - // NewRunnerJob creates a new k6 job from a CRD -func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { +func NewRunnerJob(k6 *v1alpha1.K6, index int, testRunId, token string) (*batchv1.Job, error) { name := fmt.Sprintf("%s-%d", k6.Name, index) postCommand := []string{"k6", "run"} @@ -48,8 +41,7 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { command = append(command, args...) } - script, err := newScript(k6.Spec) - + script, err := types.ParseScript(&k6.Spec) if err != nil { return nil, err } @@ -73,7 +65,7 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { command = append(command, "--paused") } - command = appendFileCheckerCommand(script, command) + command = script.UpdateCommand(command) var ( zero int64 = 0 @@ -91,6 +83,7 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { } runnerLabels := newLabels(k6.Name) + runnerLabels["runner"] = "true" if k6.Spec.Runner.Metadata.Labels != nil { for k, v := range k6.Spec.Runner.Metadata.Labels { // Order not specified if _, ok := runnerLabels[k]; !ok { @@ -113,6 +106,15 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { ports = append(ports, k6.Spec.Ports...) env := newIstioEnvVar(k6.Spec.Scuttle, istioEnabled) + if len(testRunId) > 0 { + env = append(env, corev1.EnvVar{ + Name: "K6_CLOUD_PUSH_REF_ID", + Value: testRunId, + }, corev1.EnvVar{ + Name: "K6_CLOUD_TOKEN", + Value: token, + }) + } env = append(env, k6.Spec.Runner.Env...) job := &batchv1.Job{ @@ -138,16 +140,20 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int) (*batchv1.Job, error) { NodeSelector: k6.Spec.Runner.NodeSelector, SecurityContext: &k6.Spec.Runner.SecurityContext, Containers: []corev1.Container{{ - Image: image, - Name: "k6", - Command: command, - Env: env, - Resources: k6.Spec.Runner.Resources, - VolumeMounts: newVolumeMountSpec(script), - Ports: ports, + Image: image, + Name: "k6", + Command: command, + Env: env, + Resources: k6.Spec.Runner.Resources, + VolumeMounts: []corev1.VolumeMount{ + script.VolumeMount(), + }, + Ports: ports, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -169,6 +175,7 @@ func NewRunnerService(k6 *v1alpha1.K6, index int) (*corev1.Service, error) { } runnerLabels := newLabels(k6.Name) + runnerLabels["runner"] = "true" if k6.Spec.Runner.Metadata.Labels != nil { for k, v := range k6.Spec.Runner.Metadata.Labels { // Order not specified if _, ok := runnerLabels[k]; !ok { @@ -223,85 +230,3 @@ func newAntiAffinity() *corev1.Affinity { }, } } - -func newVolumeMountSpec(s *Script) []corev1.VolumeMount { - if s.Type == "LocalFile" { - return []corev1.VolumeMount{} - } - return []corev1.VolumeMount{{ - Name: "k6-test-volume", - MountPath: "/test", - }} -} - -func newVolumeSpec(s *Script) []corev1.Volume { - switch s.Type { - case "VolumeClaim": - return []corev1.Volume{{ - Name: "k6-test-volume", - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: s.Name, - }, - }, - }} - case "ConfigMap": - return []corev1.Volume{{ - Name: "k6-test-volume", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: s.Name, - }, - }, - }, - }} - default: - return []corev1.Volume{} - } -} - -func newScript(spec v1alpha1.K6Spec) (*Script, error) { - s := &Script{} - s.File = "test.js" - - if spec.Script.VolumeClaim.Name != "" { - s.Name = spec.Script.VolumeClaim.Name - if spec.Script.VolumeClaim.File != "" { - s.File = spec.Script.VolumeClaim.File - } - - s.File = fmt.Sprintf("/test/%s", s.File) - s.Type = "VolumeClaim" - return s, nil - } - - if spec.Script.ConfigMap.Name != "" { - s.Name = spec.Script.ConfigMap.Name - - if spec.Script.ConfigMap.File != "" { - s.File = spec.Script.ConfigMap.File - } - s.File = fmt.Sprintf("/test/%s", s.File) - s.Type = "ConfigMap" - return s, nil - } - - if spec.Script.LocalFile != "" { - s.Name = "LocalFile" - s.File = spec.Script.LocalFile - s.Type = "LocalFile" - return s, nil - } - - return nil, errors.New("ConfigMap, VolumeClaim or LocalFile not provided in script definition") -} - -func appendFileCheckerCommand(s *Script, cmd []string) []string { - if s.Type == "LocalFile" { - joincmd := strings.Join(cmd, " ") - checkCommand := []string{"sh", "-c", fmt.Sprintf("if [ ! -f %v ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\n%v", s.File, joincmd)} - return checkCommand - } - return cmd -} diff --git a/pkg/types/types.go b/pkg/types/types.go new file mode 100644 index 00000000..56887e96 --- /dev/null +++ b/pkg/types/types.go @@ -0,0 +1,165 @@ +package types + +import ( + "errors" + "fmt" + "strings" + + "github.com/grafana/k6-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" +) + +// Internal type created to support Spec.script options +type Script struct { + Name string + File string + Type string // ConfigMap | VolumeClaim | LocalFile +} + +// ParseScript extracts Script data bits from K6 spec and performs basic validation +func ParseScript(spec *v1alpha1.K6Spec) (*Script, error) { + s := &Script{} + s.File = "test.js" + + if spec.Script.VolumeClaim.Name != "" { + s.Name = spec.Script.VolumeClaim.Name + if spec.Script.VolumeClaim.File != "" { + s.File = spec.Script.VolumeClaim.File + } + + s.File = fmt.Sprintf("/test/%s", s.File) + s.Type = "VolumeClaim" + return s, nil + } + + if spec.Script.ConfigMap.Name != "" { + s.Name = spec.Script.ConfigMap.Name + + if spec.Script.ConfigMap.File != "" { + s.File = spec.Script.ConfigMap.File + } + + s.File = fmt.Sprintf("/test/%s", s.File) + s.Type = "ConfigMap" + return s, nil + } + + if spec.Script.LocalFile != "" { + s.Name = "LocalFile" + s.File = spec.Script.LocalFile + s.Type = "LocalFile" + return s, nil + } + + return nil, errors.New("Script definition should contain one of: ConfigMap, VolumeClaim, LocalFile") +} + +// Volume creates a Volume spec for the script +func (s *Script) Volume() corev1.Volume { + switch s.Type { + case "VolumeClaim": + return corev1.Volume{ + Name: "k6-test-volume", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: s.Name, + }, + }, + } + + case "ConfigMap": + return corev1.Volume{ + Name: "k6-test-volume", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: s.Name, + }, + }, + }, + } + default: + return corev1.Volume{} + } +} + +// VolumeMount creates a VolumeMount spec for the script +func (s *Script) VolumeMount() corev1.VolumeMount { + if s.Type == "LocalFile" { + return corev1.VolumeMount{} + } + return corev1.VolumeMount{ + Name: "k6-test-volume", + MountPath: "/test", + } +} + +// UpdateCommand modifies command to check for script existence in case of LocalFile; +// otherwise, command remains unmodified +func (s *Script) UpdateCommand(cmd []string) []string { + if s.Type == "LocalFile" { + joincmd := strings.Join(cmd, " ") + checkCommand := []string{"sh", "-c", fmt.Sprintf("if [ ! -f %v ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\n%v", s.File, joincmd)} + return checkCommand + } + return cmd +} + +// Internal type to support k6 invocation in initialization stage. +// Not all k6 commands allow the same set of arguments so CLI is object meant to contain only the ones fit for the arhive call. +// Maybe revise this once crococonf is closer to integration? +type CLI struct { + ArchiveArgs string + // k6-operator doesn't care for most values of CLI arguments to k6, with an exception of cloud output + HasCloudOut bool +} + +func ParseCLI(spec *v1alpha1.K6Spec) *CLI { + lastArgV := func(start int, args []string) (end int) { + var nextArg bool + end = start + 1 + for !nextArg && end < len(args) { + if args[end][0] == '-' { + nextArg = true + break + } + end++ + } + return + } + + var cli CLI + + args := strings.Split(spec.Arguments, " ") + i := 0 + for i < len(args) { + args[i] = strings.TrimSpace(args[i]) + if len(args[i]) == 0 { + i++ + continue + } + if args[i][0] == '-' { + end := lastArgV(i+1, args) + + switch args[i] { + case "-o", "--out": + for j := 0; j < end; j++ { + if args[j] == "cloud" { + cli.HasCloudOut = true + } + } + case "-l", "--linger", "--no-usage-report": + // non-archive arguments, so skip them + break + default: + if len(cli.ArchiveArgs) > 0 { + cli.ArchiveArgs += " " + } + cli.ArchiveArgs += strings.Join(args[i:end], " ") + } + i = end + } + } + + return &cli +} diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go new file mode 100644 index 00000000..71f66514 --- /dev/null +++ b/pkg/types/types_test.go @@ -0,0 +1,80 @@ +package types + +import ( + "testing" + + "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/stretchr/testify/assert" +) + +func Test_ParseCLI(t *testing.T) { + tests := []struct { + name string + argLine string + cli CLI + }{ + { + "EmptyArgs", + "", + CLI{}, + }, + { + "ShortArchiveArgs", + "-u 10 -d 5", + CLI{ + ArchiveArgs: "-u 10 -d 5", + }, + }, + { + "LongArchiveArgs", + "--vus 10 --duration 5", + CLI{ + ArchiveArgs: "--vus 10 --duration 5", + }, + }, + { + "ShortNonArchiveArg", + "-u 10 -d 5 -l", + CLI{ + ArchiveArgs: "-u 10 -d 5", + }, + }, + { + "LongNonArchiveArgs", + "--vus 10 --duration 5 --linger", + CLI{ + ArchiveArgs: "--vus 10 --duration 5", + }, + }, + { + "OutWithoutCloudArgs", + "--vus 10 -o json -o csv", + CLI{ + ArchiveArgs: "--vus 10", + HasCloudOut: false, + }, + }, + { + "OutWithCloudArgs", + "--vus 10 --out json -o csv --out cloud", + CLI{ + ArchiveArgs: "--vus 10", + HasCloudOut: true, + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + spec := v1alpha1.K6Spec{ + Arguments: test.argLine, + } + cli := ParseCLI(&spec) + + assert.Equal(t, test.cli.ArchiveArgs, cli.ArchiveArgs) + assert.Equal(t, test.cli.HasCloudOut, cli.HasCloudOut) + }) + } +} From f391f82c271087cd6782c73fb4599b1de3132343 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Tue, 7 Dec 2021 15:51:39 +0200 Subject: [PATCH 02/15] cloud output: fix and add unit tests --- pkg/resources/jobs/initializer.go | 15 +- pkg/resources/jobs/initializer_test.go | 113 +++++++ pkg/resources/jobs/runner.go | 2 +- pkg/resources/jobs/runner_test.go | 412 ++++++++++++++++--------- pkg/types/types.go | 28 +- 5 files changed, 399 insertions(+), 171 deletions(-) create mode 100644 pkg/resources/jobs/initializer_test.go diff --git a/pkg/resources/jobs/initializer.go b/pkg/resources/jobs/initializer.go index 7dc4e25f..3175bdcf 100644 --- a/pkg/resources/jobs/initializer.go +++ b/pkg/resources/jobs/initializer.go @@ -53,8 +53,8 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { var ( // k6 allows to run archive command on archives too so type of file here doesn't matter - scriptName = fmt.Sprintf("/test/%s", script.File) - archiveName = fmt.Sprintf("./%s.archived.tar", script.File) + scriptName = script.FullName() + archiveName = fmt.Sprintf("./%s.archived.tar", script.Filename) ) command, istioEnabled := newIstioCommand(k6.Spec.Scuttle.Enabled, []string{"sh", "-c"}) command = append(command, fmt.Sprintf("k6 archive %s -O %s %s && k6 inspect --execution-requirements %s", @@ -84,12 +84,11 @@ func NewInitializerJob(k6 *v1alpha1.K6, argLine string) (*batchv1.Job, error) { RestartPolicy: corev1.RestartPolicyNever, Containers: []corev1.Container{ { - ImagePullPolicy: "Never", - Image: image, - Name: "k6", - Command: command, - Env: env, - Resources: k6.Spec.Runner.Resources, + Image: image, + Name: "k6", + Command: command, + Env: env, + Resources: k6.Spec.Runner.Resources, VolumeMounts: []corev1.VolumeMount{ { Name: "k6-test-volume", diff --git a/pkg/resources/jobs/initializer_test.go b/pkg/resources/jobs/initializer_test.go new file mode 100644 index 00000000..5a3fdc80 --- /dev/null +++ b/pkg/resources/jobs/initializer_test.go @@ -0,0 +1,113 @@ +package jobs + +import ( + "testing" + + deep "github.com/go-test/deep" + "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/types" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestNewInitializerJob(t *testing.T) { + script := &types.Script{ + Name: "test", + Filename: "test.js", + Type: "ConfigMap", + } + + automountServiceAccountToken := true + + expectedOutcome := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-initializer", + Namespace: "test", + Labels: map[string]string{ + "app": "k6", + "k6_cr": "test", + "label1": "awesome", + }, + Annotations: map[string]string{ + "awesomeAnnotation": "dope", + }, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "app": "k6", + "k6_cr": "test", + "label1": "awesome", + }, + Annotations: map[string]string{ + "awesomeAnnotation": "dope", + }, + }, + Spec: corev1.PodSpec{ + AutomountServiceAccountToken: &automountServiceAccountToken, + ServiceAccountName: "default", + Affinity: nil, + NodeSelector: nil, + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + Image: "ghcr.io/grafana/operator:latest-runner", + Name: "k6", + Command: []string{ + "sh", "-c", + "k6 archive /test/test.js -O ./test.js.archived.tar --out cloud && k6 inspect --execution-requirements ./test.js.archived.tar", + }, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "k6-test-volume", + MountPath: "/test", + }}, + Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, + }, + }, + Volumes: []corev1.Volume{ + script.Volume(), + }, + }, + }, + }, + } + + k6 := &v1alpha1.K6{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1alpha1.K6Spec{ + Script: v1alpha1.K6Script{ + ConfigMap: v1alpha1.K6Configmap{ + Name: "test", + File: "test.js", + }, + }, + Arguments: "--out cloud", + Runner: v1alpha1.Pod{ + Metadata: v1alpha1.PodMetadata{ + Labels: map[string]string{ + "label1": "awesome", + }, + Annotations: map[string]string{ + "awesomeAnnotation": "dope", + }, + }, + }, + }, + } + + job, err := NewInitializerJob(k6, "--out cloud") + if err != nil { + t.Errorf("NewInitializerJob errored, got: %v", err) + } + + if diff := deep.Equal(job, expectedOutcome); diff != nil { + t.Error(diff) + } +} diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index a807d14f..dc7e8de3 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -53,7 +53,7 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, testRunId, token string) (*batchv1 command = append( command, - fmt.Sprintf(script.File), + fmt.Sprintf(script.FullName()), "--address=0.0.0.0:6565") paused := true diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index 6e9aeec4..383a722f 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -7,17 +7,18 @@ import ( deep "github.com/go-test/deep" "github.com/grafana/k6-operator/api/v1alpha1" + "github.com/grafana/k6-operator/pkg/types" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestNewScriptVolumeClaim(t *testing.T) { - - expectedOutcome := &Script{ - Name: "Test", - File: "/test/thing.js", - Type: "VolumeClaim", + expectedOutcome := &types.Script{ + Name: "Test", + Path: "/test/", + Filename: "thing.js", + Type: "VolumeClaim", } k6 := v1alpha1.K6Spec{ @@ -29,9 +30,9 @@ func TestNewScriptVolumeClaim(t *testing.T) { }, } - script, err := newScript(k6) + script, err := types.ParseScript(&k6) if err != nil { - t.Errorf("NewScript with Volume Claim errored, got: %v, want: %v", err, expectedOutcome) + t.Errorf("NewScript with ConfigMap errored, got: %v, want: %v", err, expectedOutcome) } if !reflect.DeepEqual(script, expectedOutcome) { t.Errorf("NewScript with VolumeClaim failed to return expected output, got: %v, expected: %v", script, expectedOutcome) @@ -39,11 +40,11 @@ func TestNewScriptVolumeClaim(t *testing.T) { } func TestNewScriptConfigMap(t *testing.T) { - - expectedOutcome := &Script{ - Name: "Test", - File: "/test/thing.js", - Type: "ConfigMap", + expectedOutcome := &types.Script{ + Name: "Test", + Path: "/test/", + Filename: "thing.js", + Type: "ConfigMap", } k6 := v1alpha1.K6Spec{ @@ -55,7 +56,7 @@ func TestNewScriptConfigMap(t *testing.T) { }, } - script, err := newScript(k6) + script, err := types.ParseScript(&k6) if err != nil { t.Errorf("NewScript with ConfigMap errored, got: %v, want: %v", err, expectedOutcome) } @@ -66,10 +67,11 @@ func TestNewScriptConfigMap(t *testing.T) { func TestNewScriptLocalFile(t *testing.T) { - expectedOutcome := &Script{ - Name: "LocalFile", - File: "/custom/my_test.js", - Type: "LocalFile", + expectedOutcome := &types.Script{ + Name: "LocalFile", + Path: "/custom/", + Filename: "my_test.js", + Type: "LocalFile", } k6 := v1alpha1.K6Spec{ @@ -78,7 +80,7 @@ func TestNewScriptLocalFile(t *testing.T) { }, } - script, err := newScript(k6) + script, err := types.ParseScript(&k6) if err != nil { t.Errorf("NewScript with LocalFile errored, got: %v, want: %v", err, expectedOutcome) } @@ -88,39 +90,37 @@ func TestNewScriptLocalFile(t *testing.T) { } func TestNewScriptNoScript(t *testing.T) { - k6 := v1alpha1.K6Spec{} - script, err := newScript(k6) + script, err := types.ParseScript(&k6) if err == nil && script != nil { t.Errorf("Expected Error from NewScript, got: %v, want: %v", err, errors.New("configMap, VolumeClaim or LocalFile not provided in script definition")) } } func TestNewVolumeSpecVolumeClaim(t *testing.T) { - - expectedOutcome := []corev1.Volume{{ + expectedOutcome := corev1.Volume{ Name: "k6-test-volume", VolumeSource: corev1.VolumeSource{ PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ ClaimName: "test", }, }, - }} + } - k6 := &Script{ + script := &types.Script{ Type: "VolumeClaim", Name: "test", } - volumeSpec := newVolumeSpec(k6) + volumeSpec := script.Volume() if !reflect.DeepEqual(volumeSpec, expectedOutcome) { t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome) } } func TestNewVolumeSpecConfigMap(t *testing.T) { - expectedOutcome := []corev1.Volume{{ + expectedOutcome := corev1.Volume{ Name: "k6-test-volume", VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ @@ -129,27 +129,27 @@ func TestNewVolumeSpecConfigMap(t *testing.T) { }, }, }, - }} + } - k6 := &Script{ + script := &types.Script{ Type: "ConfigMap", Name: "test", } - volumeSpec := newVolumeSpec(k6) + volumeSpec := script.Volume() if !reflect.DeepEqual(volumeSpec, expectedOutcome) { t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome) } } func TestNewVolumeSpecNoType(t *testing.T) { - expectedOutcome := []corev1.Volume{} + expectedOutcome := corev1.Volume{} - k6 := &Script{ + script := &types.Script{ Name: "test", } - volumeSpec := newVolumeSpec(k6) + volumeSpec := script.Volume() if !reflect.DeepEqual(volumeSpec, expectedOutcome) { t.Errorf("VolumeSpec wasn't as expected, got: %v, expected: %v", volumeSpec, expectedOutcome) } @@ -193,6 +193,7 @@ func TestNewRunnerService(t *testing.T) { Labels: map[string]string{ "app": "k6", "k6_cr": "test", + "runner": "true", "label1": "awesome", }, Annotations: map[string]string{ @@ -239,24 +240,27 @@ func TestNewRunnerService(t *testing.T) { } func TestNewRunnerJob(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -265,11 +269,7 @@ func TestNewRunnerJob(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -295,7 +295,9 @@ func TestNewRunnerJob(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -325,7 +327,7 @@ func TestNewRunnerJob(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -335,24 +337,27 @@ func TestNewRunnerJob(t *testing.T) { } func TestNewRunnerJobNoisy(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -361,11 +366,7 @@ func TestNewRunnerJobNoisy(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -391,7 +392,9 @@ func TestNewRunnerJobNoisy(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -422,7 +425,7 @@ func TestNewRunnerJobNoisy(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -432,24 +435,27 @@ func TestNewRunnerJobNoisy(t *testing.T) { } func TestNewRunnerJobUnpaused(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -458,11 +464,7 @@ func TestNewRunnerJobUnpaused(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -488,7 +490,9 @@ func TestNewRunnerJobUnpaused(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -519,7 +523,7 @@ func TestNewRunnerJobUnpaused(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -529,24 +533,27 @@ func TestNewRunnerJobUnpaused(t *testing.T) { } func TestNewRunnerJobArguments(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -555,11 +562,7 @@ func TestNewRunnerJobArguments(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -585,7 +588,9 @@ func TestNewRunnerJobArguments(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -617,7 +622,7 @@ func TestNewRunnerJobArguments(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -627,24 +632,27 @@ func TestNewRunnerJobArguments(t *testing.T) { } func TestNewRunnerJobServiceAccount(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -653,11 +661,7 @@ func TestNewRunnerJobServiceAccount(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -683,7 +687,9 @@ func TestNewRunnerJobServiceAccount(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -716,7 +722,7 @@ func TestNewRunnerJobServiceAccount(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -726,24 +732,27 @@ func TestNewRunnerJobServiceAccount(t *testing.T) { } func TestNewRunnerJobIstio(t *testing.T) { - script := &Script{ - Name: "test", - File: "thing.js", - Type: "ConfigMap", + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -752,11 +761,7 @@ func TestNewRunnerJobIstio(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -795,7 +800,9 @@ func TestNewRunnerJobIstio(t *testing.T) { Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -828,7 +835,7 @@ func TestNewRunnerJobIstio(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } @@ -837,25 +844,27 @@ func TestNewRunnerJobIstio(t *testing.T) { } } -func TestNewRunnerJobLocalFile(t *testing.T) { - script := &Script{ - Name: "test", - File: "/test/test.js", - Type: "LocalFile", +func TestNewRunnerJobCloud(t *testing.T) { + script := &types.Script{ + Name: "test", + Filename: "thing.js", + Type: "ConfigMap", } var zero int64 = 0 automountServiceAccountToken := true + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + } + expectedOutcome := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: "test-1", Namespace: "test", - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", - }, + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -864,11 +873,108 @@ func TestNewRunnerJobLocalFile(t *testing.T) { BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "k6", - "k6_cr": "test", - "label1": "awesome", + Labels: expectedLabels, + Annotations: map[string]string{ + "awesomeAnnotation": "dope", + }, + }, + Spec: corev1.PodSpec{ + Hostname: "test-1", + RestartPolicy: corev1.RestartPolicyNever, + Affinity: nil, + NodeSelector: nil, + ServiceAccountName: "default", + AutomountServiceAccountToken: &automountServiceAccountToken, + Containers: []corev1.Container{{ + Image: "ghcr.io/grafana/operator:latest-runner", + Name: "k6", + Command: []string{"k6", "run", "--quiet", "--out", "cloud", "/test/test.js", "--address=0.0.0.0:6565", "--paused"}, + Env: []corev1.EnvVar{ + { + Name: "K6_CLOUD_PUSH_REF_ID", + Value: "testrunid", + }, + { + Name: "K6_CLOUD_TOKEN", + Value: "token", + }, + }, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{{ + Name: "k6-test-volume", + MountPath: "/test", + }}, + Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, + }}, + TerminationGracePeriodSeconds: &zero, + Volumes: []corev1.Volume{ + script.Volume(), + }, + }, + }, + }, + } + k6 := &v1alpha1.K6{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1alpha1.K6Spec{ + Script: v1alpha1.K6Script{ + ConfigMap: v1alpha1.K6Configmap{ + Name: "test", + File: "test.js", + }, + }, + Arguments: "--out cloud", + Runner: v1alpha1.Pod{ + Metadata: v1alpha1.PodMetadata{ + Annotations: map[string]string{ + "awesomeAnnotation": "dope", }, + }, + }, + }, + } + + job, err := NewRunnerJob(k6, 1, "testrunid", "token") + if err != nil { + t.Errorf("NewRunnerJob errored, got: %v", err) + } + if diff := deep.Equal(job, expectedOutcome); diff != nil { + t.Errorf("NewRunnerJob returned unexpected data, diff: %s", diff) + } +} + +func TestNewRunnerJobLocalFile(t *testing.T) { + script := &types.Script{ + Name: "test", + Filename: "/test/test.js", + Type: "LocalFile", + } + + var zero int64 = 0 + automountServiceAccountToken := true + + expectedLabels := map[string]string{ + "app": "k6", + "k6_cr": "test", + "runner": "true", + "label1": "awesome", + } + expectedOutcome := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-1", + Namespace: "test", + Labels: expectedLabels, + Annotations: map[string]string{ + "awesomeAnnotation": "dope", + }, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: expectedLabels, Annotations: map[string]string{ "awesomeAnnotation": "dope", }, @@ -882,16 +988,20 @@ func TestNewRunnerJobLocalFile(t *testing.T) { AutomountServiceAccountToken: &automountServiceAccountToken, SecurityContext: &corev1.PodSecurityContext{}, Containers: []corev1.Container{{ - Image: "ghcr.io/grafana/operator:latest-runner", - Name: "k6", - Command: []string{"sh", "-c", "if [ ! -f /test/test.js ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\nk6 run --quiet /test/test.js --address=0.0.0.0:6565 --paused"}, - Env: []corev1.EnvVar{}, - Resources: corev1.ResourceRequirements{}, - VolumeMounts: []corev1.VolumeMount{}, - Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, + Image: "ghcr.io/grafana/operator:latest-runner", + Name: "k6", + Command: []string{"sh", "-c", "if [ ! -f /test/test.js ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\nk6 run --quiet /test/test.js --address=0.0.0.0:6565 --paused"}, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{}, + VolumeMounts: []corev1.VolumeMount{ + script.VolumeMount(), + }, + Ports: []corev1.ContainerPort{{ContainerPort: 6565}}, }}, TerminationGracePeriodSeconds: &zero, - Volumes: newVolumeSpec(script), + Volumes: []corev1.Volume{ + script.Volume(), + }, }, }, }, @@ -921,7 +1031,7 @@ func TestNewRunnerJobLocalFile(t *testing.T) { }, } - job, err := NewRunnerJob(k6, 1) + job, err := NewRunnerJob(k6, 1, "", "") if err != nil { t.Errorf("NewRunnerJob errored, got: %v", err) } diff --git a/pkg/types/types.go b/pkg/types/types.go index 56887e96..2a112165 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -3,6 +3,7 @@ package types import ( "errors" "fmt" + "path/filepath" "strings" "github.com/grafana/k6-operator/api/v1alpha1" @@ -11,23 +12,25 @@ import ( // Internal type created to support Spec.script options type Script struct { - Name string - File string - Type string // ConfigMap | VolumeClaim | LocalFile + Name string // name of ConfigMap or VolumeClaim or "LocalFile" + Filename string + Path string + Type string // ConfigMap | VolumeClaim | LocalFile } // ParseScript extracts Script data bits from K6 spec and performs basic validation func ParseScript(spec *v1alpha1.K6Spec) (*Script, error) { - s := &Script{} - s.File = "test.js" + s := &Script{ + Filename: "test.js", + Path: "/test/", + } if spec.Script.VolumeClaim.Name != "" { s.Name = spec.Script.VolumeClaim.Name if spec.Script.VolumeClaim.File != "" { - s.File = spec.Script.VolumeClaim.File + s.Filename = spec.Script.VolumeClaim.File } - s.File = fmt.Sprintf("/test/%s", s.File) s.Type = "VolumeClaim" return s, nil } @@ -36,24 +39,27 @@ func ParseScript(spec *v1alpha1.K6Spec) (*Script, error) { s.Name = spec.Script.ConfigMap.Name if spec.Script.ConfigMap.File != "" { - s.File = spec.Script.ConfigMap.File + s.Filename = spec.Script.ConfigMap.File } - s.File = fmt.Sprintf("/test/%s", s.File) s.Type = "ConfigMap" return s, nil } if spec.Script.LocalFile != "" { s.Name = "LocalFile" - s.File = spec.Script.LocalFile s.Type = "LocalFile" + s.Path, s.Filename = filepath.Split(spec.Script.LocalFile) return s, nil } return nil, errors.New("Script definition should contain one of: ConfigMap, VolumeClaim, LocalFile") } +func (s *Script) FullName() string { + return s.Path + s.Filename +} + // Volume creates a Volume spec for the script func (s *Script) Volume() corev1.Volume { switch s.Type { @@ -99,7 +105,7 @@ func (s *Script) VolumeMount() corev1.VolumeMount { func (s *Script) UpdateCommand(cmd []string) []string { if s.Type == "LocalFile" { joincmd := strings.Join(cmd, " ") - checkCommand := []string{"sh", "-c", fmt.Sprintf("if [ ! -f %v ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\n%v", s.File, joincmd)} + checkCommand := []string{"sh", "-c", fmt.Sprintf("if [ ! -f %v ]; then echo \"LocalFile not found exiting...\"; exit 1; fi;\n%v", s.FullName(), joincmd)} return checkCommand } return cmd From 270e857657bb0794ffbc76e78b2b1539d8fc3e66 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Wed, 8 Dec 2021 13:34:01 +0200 Subject: [PATCH 03/15] cloud output: pass tag for cloud aggregation --- controllers/k6_create.go | 3 +++ pkg/resources/jobs/runner.go | 8 ++++++++ pkg/resources/jobs/runner_test.go | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/controllers/k6_create.go b/controllers/k6_create.go index 0157dd22..bdc3a1d2 100644 --- a/controllers/k6_create.go +++ b/controllers/k6_create.go @@ -67,6 +67,9 @@ func launchTest(ctx context.Context, k6 *v1alpha1.K6, index int, log logr.Logger return err } + log.Info(fmt.Sprintf("Runner job is ready to start with image `%s` and command `%s`", + job.Spec.Template.Spec.Containers[0].Image, job.Spec.Template.Spec.Containers[0].Command)) + if err = ctrl.SetControllerReference(k6, job, r.Scheme); err != nil { log.Error(err, "Failed to set controller reference for job") return err diff --git a/pkg/resources/jobs/runner.go b/pkg/resources/jobs/runner.go index dc7e8de3..f6f57199 100644 --- a/pkg/resources/jobs/runner.go +++ b/pkg/resources/jobs/runner.go @@ -65,6 +65,11 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, testRunId, token string) (*batchv1 command = append(command, "--paused") } + // this is a cloud output run + if len(testRunId) > 0 { + command = append(command, "--tag", fmt.Sprintf("instance_id=%d", index)) + } + command = script.UpdateCommand(command) var ( @@ -106,6 +111,8 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, testRunId, token string) (*batchv1 ports = append(ports, k6.Spec.Ports...) env := newIstioEnvVar(k6.Spec.Scuttle, istioEnabled) + + // this is a cloud output run if len(testRunId) > 0 { env = append(env, corev1.EnvVar{ Name: "K6_CLOUD_PUSH_REF_ID", @@ -115,6 +122,7 @@ func NewRunnerJob(k6 *v1alpha1.K6, index int, testRunId, token string) (*batchv1 Value: token, }) } + env = append(env, k6.Spec.Runner.Env...) job := &batchv1.Job{ diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index 383a722f..406c80ca 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -888,7 +888,7 @@ func TestNewRunnerJobCloud(t *testing.T) { Containers: []corev1.Container{{ Image: "ghcr.io/grafana/operator:latest-runner", Name: "k6", - Command: []string{"k6", "run", "--quiet", "--out", "cloud", "/test/test.js", "--address=0.0.0.0:6565", "--paused"}, + Command: []string{"k6", "run", "--quiet", "--out", "cloud", "/test/test.js", "--address=0.0.0.0:6565", "--paused", "--tag", "instance_id=1"}, Env: []corev1.EnvVar{ { Name: "K6_CLOUD_PUSH_REF_ID", From 78565b658bad32a51da734a04fe367f270ee1fc8 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Wed, 22 Dec 2021 17:41:54 +0200 Subject: [PATCH 04/15] cloud output: allow configuring projectID and test name --- README.md | 44 ++++++++++++++++++++++++++++++++++++ controllers/k6_initialize.go | 18 +++++---------- pkg/cloud/cloud.go | 26 +++++++++++++++++---- 3 files changed, 71 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 93025ee4..b15db57c 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,50 @@ Defines options for the starter pod. This includes: * passing in custom image * passing in labels and annotations +### k6 outputs + +#### k6 Cloud output + +k6 supports [output to its Cloud](https://k6.io/docs/results-visualization/cloud) with `k6 run --out cloud script.js` command. To use this option in k6-operator, set the argument in yaml: + +```yaml +... + script: + configMap: + name: "" + arguments: --out cloud +... +``` + +Then uncomment cloud output section in `config/default/kustomization.yaml` and copy your token from the Cloud there: + +```yaml +# Uncomment this section if you need cloud output and copy-paste your token +secretGenerator: +- name: cloud-token + literals: + - token= + options: + annotations: + kubernetes.io/service-account.name: k6-operator-controller + labels: + k6cloud: token +``` + +This is sufficient to run k6 with the Cloud output and default values of `projectID` and `name` (`"k6-operator-test"`). For non-default values, extended script options can be used like this: + +```js +export let options = { + ... + ext: { + loadimpact: { + name: 'Configured k6-operator test', + projectID: 1234567, + } + } +}; +``` + ### Cleaning up between test runs After completing a test run, you need to clean up the test jobs created. This is done by running the following command: ```bash diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 4928bc8e..cde3922e 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/k6-operator/pkg/cloud" "github.com/grafana/k6-operator/pkg/resources/jobs" "github.com/grafana/k6-operator/pkg/types" - k6types "go.k6.io/k6/lib/types" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -154,31 +153,26 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return false, err } - var execSpec struct { - TotalDuration k6types.NullDuration `json:"totalDuration"` - MaxVUs uint64 `json:"maxVUs"` - } + var inspectOutput cloud.InspectOutput - if err := json.Unmarshal(buf.Bytes(), &execSpec); err != nil { + if err := json.Unmarshal(buf.Bytes(), &inspectOutput); err != nil { return true, err } - log.Info(fmt.Sprintf("Execution requirements: %+v", execSpec)) + log.Info(fmt.Sprintf("k6 inspect: %+v", inspectOutput)) - if int32(execSpec.MaxVUs) < k6.Spec.Parallelism { + if int32(inspectOutput.MaxVUs) < k6.Spec.Parallelism { err = fmt.Errorf("number of instances > number of VUs") // TODO maybe change this to a warning and simply set parallelism = maxVUs and proceed with execution? // But logr doesn't seem to have warning level by default, only with V() method... // It makes sense to return to this after / during logr VS logrus issue https://github.com/grafana/k6-operator/issues/84 log.Error(err, "Parallelism argument cannot be larger than maximum VUs in the script", - "maxVUs", execSpec.MaxVUs, + "maxVUs", inspectOutput.MaxVUs, "parallelism", k6.Spec.Parallelism) return false, err } - if refID, err := cloud.CreateTestRun("k6-operator-test", token, - execSpec.MaxVUs, execSpec.TotalDuration.TimeDuration().Seconds(), - log); err != nil { + if refID, err := cloud.CreateTestRun(inspectOutput, token, log); err != nil { return true, err } else { testRunId = refID diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index f4729306..237579ee 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -9,13 +9,29 @@ import ( "go.k6.io/k6/cloudapi" "go.k6.io/k6/lib" "go.k6.io/k6/lib/consts" + "go.k6.io/k6/lib/types" ) var client *cloudapi.Client -func CreateTestRun(name string, token string, vus uint64, duration float64, log logr.Logger) (string, error) { +type InspectOutput struct { + External struct { + Loadimpact struct { + Name string `json:"name"` + ProjectID int64 `json:"projectID"` + } `json:"loadimpact"` + } `json:"ext"` + TotalDuration types.NullDuration `json:"totalDuration"` + MaxVUs uint64 `json:"maxVUs"` +} + +func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, error) { + if len(opts.External.Loadimpact.Name) < 1 { + opts.External.Loadimpact.Name = "k6-operator-test" + } + projectId := opts.External.Loadimpact.ProjectID + cloudConfig := cloudapi.NewConfig() - projectId := cloudConfig.ProjectID.ValueOrZero() logger := &logrus.Logger{ Out: os.Stdout, @@ -26,14 +42,14 @@ func CreateTestRun(name string, token string, vus uint64, duration float64, log client = cloudapi.NewClient(logger, token, cloudConfig.Host.String, consts.Version, time.Duration(time.Minute)) resp, err := client.CreateTestRun(&cloudapi.TestRun{ - Name: name, + Name: opts.External.Loadimpact.Name, ProjectID: projectId, - VUsMax: int64(vus), + VUsMax: int64(opts.MaxVUs), Thresholds: map[string][]string{}, // This is heuristic increase of duration to take into account that it takes time to start the pods. // By current observations, it shouldn't matter that much since we're sending a finish call in the end, // but it would be good to come up with another solution. - Duration: int64(duration) * 2, + Duration: int64(opts.TotalDuration.TimeDuration().Seconds()) * 2, }) if err != nil { From 2c027965ea4c83126badc23f973a9f365eee52d6 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 11 Feb 2022 13:00:28 +0200 Subject: [PATCH 05/15] cloud output: add thresholds processing --- pkg/cloud/cloud.go | 56 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index 237579ee..73e7da04 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -1,6 +1,7 @@ package cloud import ( + "fmt" "os" "time" @@ -10,6 +11,7 @@ import ( "go.k6.io/k6/lib" "go.k6.io/k6/lib/consts" "go.k6.io/k6/lib/types" + "gopkg.in/guregu/null.v3" ) var client *cloudapi.Client @@ -21,18 +23,31 @@ type InspectOutput struct { ProjectID int64 `json:"projectID"` } `json:"loadimpact"` } `json:"ext"` - TotalDuration types.NullDuration `json:"totalDuration"` - MaxVUs uint64 `json:"maxVUs"` + TotalDuration types.NullDuration `json:"totalDuration"` + MaxVUs uint64 `json:"maxVUs"` + Thresholds map[string][]string `json:"thresholds,omitempty"` +} + +type TestRun struct { + Name string `json:"name"` + ProjectID int64 `json:"project_id,omitempty"` + VUsMax int64 `json:"vus"` + Thresholds map[string][]string `json:"thresholds"` + Duration int64 `json:"duration"` + ProcessThresholds bool `json:"process_thresholds"` } func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, error) { if len(opts.External.Loadimpact.Name) < 1 { opts.External.Loadimpact.Name = "k6-operator-test" } - projectId := opts.External.Loadimpact.ProjectID cloudConfig := cloudapi.NewConfig() + if opts.External.Loadimpact.ProjectID > 0 { + cloudConfig.ProjectID = null.NewInt(opts.External.Loadimpact.ProjectID, true) + } + logger := &logrus.Logger{ Out: os.Stdout, Formatter: new(logrus.TextFormatter), @@ -40,16 +55,21 @@ func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, e Level: logrus.InfoLevel, } + if opts.Thresholds == nil { + opts.Thresholds = make(map[string][]string) + } + client = cloudapi.NewClient(logger, token, cloudConfig.Host.String, consts.Version, time.Duration(time.Minute)) - resp, err := client.CreateTestRun(&cloudapi.TestRun{ + resp, err := createTestRun(client, &TestRun{ Name: opts.External.Loadimpact.Name, - ProjectID: projectId, + ProjectID: cloudConfig.ProjectID.Int64, VUsMax: int64(opts.MaxVUs), - Thresholds: map[string][]string{}, + Thresholds: opts.Thresholds, // This is heuristic increase of duration to take into account that it takes time to start the pods. // By current observations, it shouldn't matter that much since we're sending a finish call in the end, // but it would be good to come up with another solution. - Duration: int64(opts.TotalDuration.TimeDuration().Seconds()) * 2, + Duration: int64(opts.TotalDuration.TimeDuration().Seconds()) * 2, + ProcessThresholds: true, }) if err != nil { @@ -59,6 +79,28 @@ func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, e return resp.ReferenceID, nil } +// We cannot use cloudapi.TestRun struct and cloudapi.Client.CreateTestRun call because they're not aware of +// process_thresholds argument; so let's use custom struct and function instead +func createTestRun(client *cloudapi.Client, testRun *TestRun) (*cloudapi.CreateTestRunResponse, error) { + url := "https://ingest.k6.io/v1/tests" + req, err := client.NewRequest("POST", url, testRun) + if err != nil { + return nil, err + } + + ctrr := cloudapi.CreateTestRunResponse{} + err = client.Do(req, &ctrr) + if err != nil { + return nil, err + } + + if ctrr.ReferenceID == "" { + return nil, fmt.Errorf("failed to get a reference ID") + } + + return &ctrr, nil +} + func FinishTestRun(refID string) error { return client.TestFinished(refID, cloudapi.ThresholdResult( map[string]map[string]bool{}, From e23f9dd4e5b2a5a975b64e684f25a8a5773f6fdf Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Tue, 1 Mar 2022 19:06:07 +0200 Subject: [PATCH 06/15] cloud output: add handling of optional host env var --- controllers/k6_initialize.go | 13 ++++++++++++- pkg/cloud/cloud.go | 14 +++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index cde3922e..710c0608 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -172,7 +172,9 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return false, err } - if refID, err := cloud.CreateTestRun(inspectOutput, token, log); err != nil { + host := getEnvVar(k6.Spec.Runner.Env, "K6_CLOUD_HOST") + + if refID, err := cloud.CreateTestRun(inspectOutput, host, token, log); err != nil { return true, err } else { testRunId = refID @@ -195,3 +197,12 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return res, nil } + +func getEnvVar(vars []corev1.EnvVar, name string) string { + for _, v := range vars { + if v.Name == name { + return v.Value + } + } + return "" +} diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index 73e7da04..c0b61a21 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -37,7 +37,7 @@ type TestRun struct { ProcessThresholds bool `json:"process_thresholds"` } -func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, error) { +func CreateTestRun(opts InspectOutput, host, token string, log logr.Logger) (string, error) { if len(opts.External.Loadimpact.Name) < 1 { opts.External.Loadimpact.Name = "k6-operator-test" } @@ -59,8 +59,12 @@ func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, e opts.Thresholds = make(map[string][]string) } - client = cloudapi.NewClient(logger, token, cloudConfig.Host.String, consts.Version, time.Duration(time.Minute)) - resp, err := createTestRun(client, &TestRun{ + if len(host) == 0 { + host = cloudConfig.Host.String + } + + client = cloudapi.NewClient(logger, token, host, consts.Version, time.Duration(time.Minute)) + resp, err := createTestRun(client, host, &TestRun{ Name: opts.External.Loadimpact.Name, ProjectID: cloudConfig.ProjectID.Int64, VUsMax: int64(opts.MaxVUs), @@ -81,8 +85,8 @@ func CreateTestRun(opts InspectOutput, token string, log logr.Logger) (string, e // We cannot use cloudapi.TestRun struct and cloudapi.Client.CreateTestRun call because they're not aware of // process_thresholds argument; so let's use custom struct and function instead -func createTestRun(client *cloudapi.Client, testRun *TestRun) (*cloudapi.CreateTestRunResponse, error) { - url := "https://ingest.k6.io/v1/tests" +func createTestRun(client *cloudapi.Client, host string, testRun *TestRun) (*cloudapi.CreateTestRunResponse, error) { + url := host + "/v1/tests" req, err := client.NewRequest("POST", url, testRun) if err != nil { return nil, err From f2519a5e7f7e5f250b4bd1e1d35e01921895a72a Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Wed, 9 Mar 2022 15:49:59 +0200 Subject: [PATCH 07/15] cloud output: fix panic on one-value arguments --- pkg/types/types.go | 2 +- pkg/types/types_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/types/types.go b/pkg/types/types.go index 2a112165..e0e45dfc 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -123,7 +123,7 @@ type CLI struct { func ParseCLI(spec *v1alpha1.K6Spec) *CLI { lastArgV := func(start int, args []string) (end int) { var nextArg bool - end = start + 1 + end = start for !nextArg && end < len(args) { if args[end][0] == '-' { nextArg = true diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go index 71f66514..e61733b3 100644 --- a/pkg/types/types_test.go +++ b/pkg/types/types_test.go @@ -62,6 +62,14 @@ func Test_ParseCLI(t *testing.T) { HasCloudOut: true, }, }, + { + "VerboseOutWithCloudArgs", + "--vus 10 --out json -o csv --out cloud --verbose", + CLI{ + ArchiveArgs: "--vus 10 --verbose", + HasCloudOut: true, + }, + }, } for _, test := range tests { From e1f930be56d1fe5184d5e1ca150c4643c7ceb114 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 25 Mar 2022 15:07:58 +0200 Subject: [PATCH 08/15] cloud output: add logging --- controllers/k6_initialize.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 710c0608..c051b79f 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -156,6 +156,8 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 var inspectOutput cloud.InspectOutput if err := json.Unmarshal(buf.Bytes(), &inspectOutput); err != nil { + // this shouldn't normally happen to if it does, let's log output by default + log.Error(err, fmt.Sprintf("unable to marshal: `%s`", buf.String())) return true, err } From aec7aedbcb57fa488c387918e3740c9c03b5f241 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 1 Apr 2022 15:08:10 +0300 Subject: [PATCH 09/15] cloud output: fix unit tests after rebase --- pkg/resources/jobs/runner_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/resources/jobs/runner_test.go b/pkg/resources/jobs/runner_test.go index 406c80ca..b532823c 100644 --- a/pkg/resources/jobs/runner_test.go +++ b/pkg/resources/jobs/runner_test.go @@ -884,6 +884,7 @@ func TestNewRunnerJobCloud(t *testing.T) { Affinity: nil, NodeSelector: nil, ServiceAccountName: "default", + SecurityContext: &corev1.PodSecurityContext{}, AutomountServiceAccountToken: &automountServiceAccountToken, Containers: []corev1.Container{{ Image: "ghcr.io/grafana/operator:latest-runner", @@ -972,6 +973,7 @@ func TestNewRunnerJobLocalFile(t *testing.T) { }, }, Spec: batchv1.JobSpec{ + BackoffLimit: new(int32), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: expectedLabels, From 849a0ab9141ceecb0333a761d49040ad56602d51 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 1 Apr 2022 16:38:50 +0300 Subject: [PATCH 10/15] cloud output: send instances on test creation --- controllers/k6_initialize.go | 2 +- pkg/cloud/cloud.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index c051b79f..380c48d8 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -176,7 +176,7 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 host := getEnvVar(k6.Spec.Runner.Env, "K6_CLOUD_HOST") - if refID, err := cloud.CreateTestRun(inspectOutput, host, token, log); err != nil { + if refID, err := cloud.CreateTestRun(inspectOutput, k6.Spec.Parallelism, host, token, log); err != nil { return true, err } else { testRunId = refID diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index c0b61a21..9a85e16e 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -35,9 +35,10 @@ type TestRun struct { Thresholds map[string][]string `json:"thresholds"` Duration int64 `json:"duration"` ProcessThresholds bool `json:"process_thresholds"` + Instances int32 `json:"instances"` } -func CreateTestRun(opts InspectOutput, host, token string, log logr.Logger) (string, error) { +func CreateTestRun(opts InspectOutput, instances int32, host, token string, log logr.Logger) (string, error) { if len(opts.External.Loadimpact.Name) < 1 { opts.External.Loadimpact.Name = "k6-operator-test" } @@ -74,6 +75,7 @@ func CreateTestRun(opts InspectOutput, host, token string, log logr.Logger) (str // but it would be good to come up with another solution. Duration: int64(opts.TotalDuration.TimeDuration().Seconds()) * 2, ProcessThresholds: true, + Instances: instances, }) if err != nil { From ae3287f6fd3f347138610207f56f4fabe970c741 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 22 Apr 2022 18:38:04 +0300 Subject: [PATCH 11/15] cloud output: re-set duration of test --- pkg/cloud/cloud.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pkg/cloud/cloud.go b/pkg/cloud/cloud.go index 9a85e16e..6e8965cd 100644 --- a/pkg/cloud/cloud.go +++ b/pkg/cloud/cloud.go @@ -66,14 +66,11 @@ func CreateTestRun(opts InspectOutput, instances int32, host, token string, log client = cloudapi.NewClient(logger, token, host, consts.Version, time.Duration(time.Minute)) resp, err := createTestRun(client, host, &TestRun{ - Name: opts.External.Loadimpact.Name, - ProjectID: cloudConfig.ProjectID.Int64, - VUsMax: int64(opts.MaxVUs), - Thresholds: opts.Thresholds, - // This is heuristic increase of duration to take into account that it takes time to start the pods. - // By current observations, it shouldn't matter that much since we're sending a finish call in the end, - // but it would be good to come up with another solution. - Duration: int64(opts.TotalDuration.TimeDuration().Seconds()) * 2, + Name: opts.External.Loadimpact.Name, + ProjectID: cloudConfig.ProjectID.Int64, + VUsMax: int64(opts.MaxVUs), + Thresholds: opts.Thresholds, + Duration: int64(opts.TotalDuration.TimeDuration().Seconds()), ProcessThresholds: true, Instances: instances, }) From a2d962cedfbbe719594a153c7edbd1c0ac7a41e8 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 29 Apr 2022 13:56:15 +0300 Subject: [PATCH 12/15] cloud output: fix for verbose flag in argument line --- pkg/types/types.go | 9 +++++++-- pkg/types/types_test.go | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/types/types.go b/pkg/types/types.go index e0e45dfc..6744776b 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -111,8 +111,9 @@ func (s *Script) UpdateCommand(cmd []string) []string { return cmd } -// Internal type to support k6 invocation in initialization stage. -// Not all k6 commands allow the same set of arguments so CLI is object meant to contain only the ones fit for the arhive call. +// CLI is an innternal type to support k6 invocation in initialization stage. +// Not all k6 commands allow the same set of arguments so CLI is an object +// meant to contain only the ones fit for the archive call. // Maybe revise this once crococonf is closer to integration? type CLI struct { ArchiveArgs string @@ -157,6 +158,10 @@ func ParseCLI(spec *v1alpha1.K6Spec) *CLI { case "-l", "--linger", "--no-usage-report": // non-archive arguments, so skip them break + case "--verbose", "-v": + // this argument is acceptable by archive but it'd + // mess up the JSON output of `k6 inspect` + break default: if len(cli.ArchiveArgs) > 0 { cli.ArchiveArgs += " " diff --git a/pkg/types/types_test.go b/pkg/types/types_test.go index e61733b3..07fc21d9 100644 --- a/pkg/types/types_test.go +++ b/pkg/types/types_test.go @@ -66,7 +66,7 @@ func Test_ParseCLI(t *testing.T) { "VerboseOutWithCloudArgs", "--vus 10 --out json -o csv --out cloud --verbose", CLI{ - ArchiveArgs: "--vus 10 --verbose", + ArchiveArgs: "--vus 10", HasCloudOut: true, }, }, From 413dcd0d6db1b8131378a17d8d8161ae7134d20a Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 29 Apr 2022 18:08:41 +0300 Subject: [PATCH 13/15] cloud output: update docs --- README.md | 4 +++- controllers/k6_controller.go | 2 ++ controllers/k6_initialize.go | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b15db57c..b41550c4 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,9 @@ Defines options for the starter pod. This includes: #### k6 Cloud output -k6 supports [output to its Cloud](https://k6.io/docs/results-visualization/cloud) with `k6 run --out cloud script.js` command. To use this option in k6-operator, set the argument in yaml: +k6 supports [output to its Cloud](https://k6.io/docs/results-visualization/cloud) with `k6 run --out cloud script.js` command. This feature is available in k6-operator as well for subscribed users. Note that it supports only `parallelism: 20` or less. + +To use this option in k6-operator, set the argument in yaml: ```yaml ... diff --git a/controllers/k6_controller.go b/controllers/k6_controller.go index d171fa69..03a2e161 100644 --- a/controllers/k6_controller.go +++ b/controllers/k6_controller.go @@ -63,6 +63,8 @@ func (r *K6Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return InitializeJobs(ctx, log, k6, r) case "initialization": // here we're just waiting until initialize is done + // Note: it is present as a separate stage to ensure there's only one + // initialization job at a time return ctrl.Result{}, nil case "initialized": return CreateJobs(ctx, log, k6, r) diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 380c48d8..6684cf00 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -156,7 +156,7 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 var inspectOutput cloud.InspectOutput if err := json.Unmarshal(buf.Bytes(), &inspectOutput); err != nil { - // this shouldn't normally happen to if it does, let's log output by default + // this shouldn't normally happen but if it does, let's log output by default log.Error(err, fmt.Sprintf("unable to marshal: `%s`", buf.String())) return true, err } From f08da61c27776c2fe89b325566751be5026ff059 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 29 Apr 2022 18:51:32 +0300 Subject: [PATCH 14/15] cloud output: fix for indefinite run of FinishJobs step No matter the status of the pods, FinishJobs must be able to a) finalize the cloud output test and b) proceed to the next stage of controller. --- controllers/k6_finish.go | 84 +++++++++++++++++++++++------------- controllers/k6_initialize.go | 7 ++- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/controllers/k6_finish.go b/controllers/k6_finish.go index 4378405e..44671e18 100644 --- a/controllers/k6_finish.go +++ b/controllers/k6_finish.go @@ -3,6 +3,7 @@ package controllers import ( "context" "fmt" + "time" "github.com/go-logr/logr" "github.com/grafana/k6-operator/api/v1alpha1" @@ -10,6 +11,7 @@ import ( "github.com/grafana/k6-operator/pkg/types" batchv1 "k8s.io/api/batch/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -18,46 +20,68 @@ import ( func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (ctrl.Result, error) { log.Info("Waiting for pods to finish") - selector := labels.SelectorFromSet(map[string]string{ - "app": "k6", - "k6_cr": k6.Name, - "runner": "true", - }) + // Here we assume that the test runs for some time and there is no need to + // check it more often than twice in a minute. + // + // The total timeout for the test is set to duration of the test + 2 min. + // These 2 min are meant to cover the time needed to start the pods: sometimes + // pods are ready a bit later than operator reaches this stage so from the + // viewpoint of operator it takes longer. This behaviour depends on the setup of + // cluster. 2 min are meant to be a sufficient safeguard for such cases. - opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} - jl := &batchv1.JobList{} + testDuration := inspectOutput.TotalDuration.TimeDuration() - if err := r.List(ctx, jl, opts); err != nil { - log.Error(err, "Could not list jobs") - return ctrl.Result{}, err - } + err := wait.PollImmediate(time.Second*30, testDuration+time.Minute*2, func() (done bool, err error) { + selector := labels.SelectorFromSet(map[string]string{ + "app": "k6", + "k6_cr": k6.Name, + "runner": "true", + }) + + opts := &client.ListOptions{LabelSelector: selector, Namespace: k6.Namespace} + jl := &batchv1.JobList{} - //TODO: We should distinguish between Suceeded/Failed/Unknown - var finished int32 - for _, job := range jl.Items { - if job.Status.Active != 0 { - continue + if err := r.List(ctx, jl, opts); err != nil { + log.Error(err, "Could not list jobs") + return false, nil + } + + // TODO: We should distinguish between Suceeded/Failed/Unknown + var finished int32 + for _, job := range jl.Items { + if job.Status.Active != 0 { + continue + } + finished++ } - finished++ - } - log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.Spec.Parallelism)) + log.Info(fmt.Sprintf("%d/%d jobs complete", finished, k6.Spec.Parallelism)) - if finished >= k6.Spec.Parallelism { - k6.Status.Stage = "finished" - if err := r.Client.Status().Update(ctx, k6); err != nil { - log.Error(err, "Could not update status of custom resource") - return ctrl.Result{}, err + if finished >= k6.Spec.Parallelism { + return true, nil } - if cli := types.ParseCLI(&k6.Spec); cli.HasCloudOut { - if err := cloud.FinishTestRun(testRunId); err != nil { - log.Error(err, "Could not finish test run with cloud output") - return ctrl.Result{}, err - } + return false, nil + }) + + if err != nil { + log.Error(err, "Waiting for pods to finish ended with error") + } + + // If this is a test run with cloud output, try to finalize it regardless. + if cli := types.ParseCLI(&k6.Spec); cli.HasCloudOut { + if err = cloud.FinishTestRun(testRunId); err != nil { + log.Error(err, "Could not finalize the test run with cloud output") + } else { + log.Info(fmt.Sprintf("Cloud test run %s was finalized succesfully", testRunId)) } + } - log.Info(fmt.Sprintf("Cloud test run %s was finished", testRunId)) + k6.Status.Stage = "finished" + if err = r.Client.Status().Update(ctx, k6); err != nil { + log.Error(err, "Could not update status of custom resource") + return ctrl.Result{}, err } + return ctrl.Result{}, nil } diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 6684cf00..5d0513d0 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -26,8 +26,9 @@ import ( // k6 Cloud related vars // Right now operator works with one test at a time so these should be safe. var ( - testRunId string - token string + testRunId string + token string + inspectOutput cloud.InspectOutput ) // InitializeJobs creates jobs that will run initial checks for distributed test if any are necessary @@ -153,8 +154,6 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return false, err } - var inspectOutput cloud.InspectOutput - if err := json.Unmarshal(buf.Bytes(), &inspectOutput); err != nil { // this shouldn't normally happen but if it does, let's log output by default log.Error(err, fmt.Sprintf("unable to marshal: `%s`", buf.String())) From b687b0445b4d41563522dfb69e72ee7bf5509428 Mon Sep 17 00:00:00 2001 From: Olha Yevtushenko Date: Fri, 6 May 2022 17:06:58 +0300 Subject: [PATCH 15/15] cloud output: add logging of stages --- controllers/k6_create.go | 1 + controllers/k6_finish.go | 1 + controllers/k6_initialize.go | 2 ++ controllers/k6_start.go | 1 + 4 files changed, 5 insertions(+) diff --git a/controllers/k6_create.go b/controllers/k6_create.go index bdc3a1d2..12c5479b 100644 --- a/controllers/k6_create.go +++ b/controllers/k6_create.go @@ -25,6 +25,7 @@ func CreateJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco return res, err } + log.Info("Changing stage of K6 status to created") k6.Status.Stage = "created" if err = r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource") diff --git a/controllers/k6_finish.go b/controllers/k6_finish.go index 44671e18..8dffd10f 100644 --- a/controllers/k6_finish.go +++ b/controllers/k6_finish.go @@ -77,6 +77,7 @@ func FinishJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reco } } + log.Info("Changing stage of K6 status to finished") k6.Status.Stage = "finished" if err = r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource") diff --git a/controllers/k6_initialize.go b/controllers/k6_initialize.go index 5d0513d0..8d804a12 100644 --- a/controllers/k6_initialize.go +++ b/controllers/k6_initialize.go @@ -35,6 +35,7 @@ var ( func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Reconciler) (res ctrl.Result, err error) { log.Info("Initialize test") + log.Info("Changing stage of K6 status to initialization") k6.Status.Stage = "initialization" if err = r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource") @@ -190,6 +191,7 @@ func InitializeJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6 return } + log.Info("Changing stage of K6 status to initialized") k6.Status.Stage = "initialized" if err = r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource") diff --git a/controllers/k6_start.go b/controllers/k6_start.go index 10af9b7a..4cc90f14 100644 --- a/controllers/k6_start.go +++ b/controllers/k6_start.go @@ -96,6 +96,7 @@ func StartJobs(ctx context.Context, log logr.Logger, k6 *v1alpha1.K6, r *K6Recon return ctrl.Result{}, err } + log.Info("Changing stage of K6 status to started") k6.Status.Stage = "started" if err = r.Client.Status().Update(ctx, k6); err != nil { log.Error(err, "Could not update status of custom resource")