diff --git a/operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml b/operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml index 92124622c8..bb97285791 100644 --- a/operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml +++ b/operator/config/crd/bases/metrics.keptn.sh_keptnmetrics.yaml @@ -16,7 +16,7 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .spec.source + - jsonPath: .spec.provider.name name: Provider type: string - jsonPath: .spec.query diff --git a/operator/config/rbac/extra_role_binding.yaml b/operator/config/rbac/extra_role_binding.yaml new file mode 100644 index 0000000000..c5acd3007b --- /dev/null +++ b/operator/config/rbac/extra_role_binding.yaml @@ -0,0 +1,13 @@ +## This is not autogenerated +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: manager-role +subjects: + - kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/operator/config/rbac/kustomization.yaml b/operator/config/rbac/kustomization.yaml index 731832a6ac..cb0a597a69 100644 --- a/operator/config/rbac/kustomization.yaml +++ b/operator/config/rbac/kustomization.yaml @@ -7,6 +7,7 @@ resources: - service_account.yaml - role.yaml - role_binding.yaml +- extra_role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml # Comment the following 4 lines if you want to disable diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml index afa6e832ff..e265d4e2e5 100644 --- a/operator/config/rbac/role.yaml +++ b/operator/config/rbac/role.yaml @@ -79,12 +79,6 @@ rules: - get - list - watch -- apiGroups: - - "" - resources: - - secrets - verbs: - - get - apiGroups: - lifecycle.keptn.sh resources: @@ -314,12 +308,8 @@ rules: resources: - keptnmetrics verbs: - - create - - delete - get - list - - patch - - update - watch - apiGroups: - metrics.keptn.sh @@ -335,3 +325,25 @@ rules: - get - patch - update +- apiGroups: + - metrics.keptn.sh + resources: + - providers + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + creationTimestamp: null + name: manager-role + namespace: keptn-lifecycle-toolkit-system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/dynatrace.go b/operator/controllers/common/providers/dynatrace.go similarity index 93% rename from operator/controllers/lifecycle/keptnevaluation/providers/dynatrace.go rename to operator/controllers/common/providers/dynatrace.go index 7eb80cb5d4..0c96b8a702 100644 --- a/operator/controllers/lifecycle/keptnevaluation/providers/dynatrace.go +++ b/operator/controllers/common/providers/dynatrace.go @@ -38,7 +38,7 @@ type DynatraceData struct { Values []*float64 `json:"values"` } -func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, error) { +func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) { qURL := provider.Spec.TargetServer + "/api/v2/metrics/query?metricSelector=" + objective.Query d.Log.Info("Running query: " + qURL) @@ -47,19 +47,19 @@ func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, objective kl req, err := http.NewRequestWithContext(ctx, "GET", qURL, nil) if err != nil { d.Log.Error(err, "Error while creating request") - return "", err + return "", nil, err } token, err := d.getDTApiToken(ctx, provider) if err != nil { - return "", err + return "", nil, err } req.Header.Set("Authorization", "Api-Token "+token) res, err := d.httpClient.Do(req) if err != nil { d.Log.Error(err, "Error while creating request") - return "", err + return "", nil, err } defer func() { err := res.Body.Close() @@ -74,10 +74,11 @@ func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, objective kl err = json.Unmarshal(b, &result) if err != nil { d.Log.Error(err, "Error while parsing response") - return "", err + return "", nil, err } - return fmt.Sprintf("%f", d.getSingleValue(result)), nil + r := fmt.Sprintf("%f", d.getSingleValue(result)) + return r, b, nil } func (d *KeptnDynatraceProvider) getSingleValue(result DynatraceResponse) float64 { diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/dynatrace_test.go b/operator/controllers/common/providers/dynatrace_test.go similarity index 94% rename from operator/controllers/lifecycle/keptnevaluation/providers/dynatrace_test.go rename to operator/controllers/common/providers/dynatrace_test.go index 168e118611..1da95d978c 100644 --- a/operator/controllers/lifecycle/keptnevaluation/providers/dynatrace_test.go +++ b/operator/controllers/common/providers/dynatrace_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" ) @@ -133,7 +134,11 @@ func TestEvaluateQuery_CorrectHTTP(t *testing.T) { TargetServer: svr.URL, }, } - _, _ = kdp.EvaluateQuery(context.TODO(), obj, p) + r, raw, e := kdp.EvaluateQuery(context.TODO(), obj, p) + require.True(t, errors.IsNotFound(e)) + require.Equal(t, []byte(nil), raw) + require.Equal(t, "", r) + } func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { @@ -173,8 +178,9 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) { TargetServer: svr.URL, }, } - r, e := kdp.EvaluateQuery(context.TODO(), obj, p) + r, raw, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.Equal(t, "", r) + require.Equal(t, []byte(nil), raw) require.NotNil(t, e) } @@ -199,7 +205,7 @@ func TestEvaluateQuery_MissingSecret(t *testing.T) { TargetServer: svr.URL, }, } - _, e := kdp.EvaluateQuery(context.TODO(), obj, p) + _, _, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.NotNil(t, e) require.True(t, strings.Contains(e.Error(), "the SecretKeyRef property with the DT API token is missing")) } @@ -231,9 +237,9 @@ func TestEvaluateQuery_SecretNotFound(t *testing.T) { TargetServer: svr.URL, }, } - _, e := kdp.EvaluateQuery(context.TODO(), obj, p) + _, _, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.NotNil(t, e) - require.True(t, strings.Contains(e.Error(), "not found")) + require.True(t, errors.IsNotFound(e)) } func TestEvaluateQuery_RefNotExistingKey(t *testing.T) { @@ -275,7 +281,7 @@ func TestEvaluateQuery_RefNotExistingKey(t *testing.T) { }, } - _, e := kdp.EvaluateQuery(context.TODO(), obj, p) + _, _, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.NotNil(t, e) require.True(t, strings.Contains(e.Error(), "invalid key "+missingKey)) } @@ -317,7 +323,8 @@ func TestEvaluateQuery_HappyPath(t *testing.T) { TargetServer: svr.URL, }, } - r, e := kdp.EvaluateQuery(context.TODO(), obj, p) + r, raw, e := kdp.EvaluateQuery(context.TODO(), obj, p) require.Nil(t, e) + require.Equal(t, []byte(dtpayload), raw) require.Equal(t, fmt.Sprintf("%f", 50.0), r) } diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/prometheus.go b/operator/controllers/common/providers/prometheus.go similarity index 81% rename from operator/controllers/lifecycle/keptnevaluation/providers/prometheus.go rename to operator/controllers/common/providers/prometheus.go index c2a6dd7685..20f0339c64 100644 --- a/operator/controllers/lifecycle/keptnevaluation/providers/prometheus.go +++ b/operator/controllers/common/providers/prometheus.go @@ -18,7 +18,7 @@ type KeptnPrometheusProvider struct { httpClient http.Client } -func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, error) { +func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) { ctx, cancel := context.WithTimeout(ctx, 20*time.Second) defer cancel() @@ -26,7 +26,7 @@ func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective k r.Log.Info("Running query: /api/v1/query?query=" + objective.Query + "&time=" + queryTime.String()) client, err := promapi.NewClient(promapi.Config{Address: provider.Spec.TargetServer, Client: &r.httpClient}) if err != nil { - return "", err + return "", nil, err } api := prometheus.NewAPI(client) result, w, err := api.Query( @@ -37,7 +37,7 @@ func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective k ) if err != nil { - return "", err + return "", nil, err } if len(w) != 0 { @@ -47,18 +47,22 @@ func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective k // check if we can cast the result to a vector, it might be another data struct which we can't process resultVector, ok := result.(model.Vector) if !ok { - return "", fmt.Errorf("could not cast result") + return "", nil, fmt.Errorf("could not cast result") } // We are only allowed to return one value, if not the query may be malformed // we are using two different errors to give the user more information about the result if len(resultVector) == 0 { r.Log.Info("No values in query result") - return "", fmt.Errorf("no values in query result") + return "", nil, fmt.Errorf("no values in query result") } else if len(resultVector) > 1 { r.Log.Info("Too many values in the query result") - return "", fmt.Errorf("too many values in the query result") + return "", nil, fmt.Errorf("too many values in the query result") } value := resultVector[0].Value.String() - return value, nil + b, err := resultVector[0].Value.MarshalJSON() + if err != nil { + return "", nil, err + } + return value, b, nil } diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/prometheus_test.go b/operator/controllers/common/providers/prometheus_test.go similarity index 96% rename from operator/controllers/lifecycle/keptnevaluation/providers/prometheus_test.go rename to operator/controllers/common/providers/prometheus_test.go index 2231760d1b..b0d8a7aede 100644 --- a/operator/controllers/lifecycle/keptnevaluation/providers/prometheus_test.go +++ b/operator/controllers/common/providers/prometheus_test.go @@ -23,6 +23,7 @@ func Test_prometheus(t *testing.T) { name string in string out string + outraw []byte wantError bool }{ { @@ -35,6 +36,7 @@ func Test_prometheus(t *testing.T) { name: "warnings", in: promWarnPayload, out: "1", + outraw: []byte("\"1\""), wantError: false, }, { @@ -59,6 +61,7 @@ func Test_prometheus(t *testing.T) { name: "happy path", in: promPayload, out: "1", + outraw: []byte("\"1\""), wantError: false, }, } @@ -89,8 +92,9 @@ func Test_prometheus(t *testing.T) { TargetServer: svr.URL, }, } - r, e := kpp.EvaluateQuery(context.TODO(), obj, p) + r, raw, e := kpp.EvaluateQuery(context.TODO(), obj, p) require.Equal(t, tt.out, r) + require.Equal(t, tt.outraw, raw) if tt.wantError != (e != nil) { t.Errorf("want error: %t, got: %v", tt.wantError, e) } diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/provider.go b/operator/controllers/common/providers/provider.go similarity index 92% rename from operator/controllers/lifecycle/keptnevaluation/providers/provider.go rename to operator/controllers/common/providers/provider.go index d49918a174..7a152a913c 100644 --- a/operator/controllers/lifecycle/keptnevaluation/providers/provider.go +++ b/operator/controllers/common/providers/provider.go @@ -13,7 +13,7 @@ import ( // KeptnSLIProvider is the interface that describes the operations that an SLI provider must implement type KeptnSLIProvider interface { - EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, error) + EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) } // NewProvider is a factory method that chooses the right implementation of KeptnSLIProvider diff --git a/operator/controllers/lifecycle/keptnevaluation/providers/provider_test.go b/operator/controllers/common/providers/provider_test.go similarity index 100% rename from operator/controllers/lifecycle/keptnevaluation/providers/provider_test.go rename to operator/controllers/common/providers/provider_test.go diff --git a/operator/controllers/lifecycle/keptnevaluation/controller.go b/operator/controllers/lifecycle/keptnevaluation/controller.go index 2f6aa17a8c..b0cb8450e5 100644 --- a/operator/controllers/lifecycle/keptnevaluation/controller.go +++ b/operator/controllers/lifecycle/keptnevaluation/controller.go @@ -24,8 +24,8 @@ import ( klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2/common" controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" + "github.com/keptn/lifecycle-toolkit/operator/controllers/common/providers" controllererrors "github.com/keptn/lifecycle-toolkit/operator/controllers/errors" - providers "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnevaluation/providers" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" @@ -50,12 +50,15 @@ type KeptnEvaluationReconciler struct { Tracer trace.Tracer } +//clusterrole //+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnevaluations,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnevaluations/status,verbs=get;update;patch //+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnevaluations/finalizers,verbs=update //+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnevaluationproviders,verbs=get;list;watch //+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptnevaluationdefinitions,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get + +//role +//+kubebuilder:rbac:groups=core,namespace=keptn-lifecycle-toolkit-system,resources=secrets,verbs=get // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -202,7 +205,7 @@ func (r *KeptnEvaluationReconciler) evaluateObjective(ctx context.Context, evalu return newStatus, statusSummary } // resolving the SLI value - value, err := provider.EvaluateQuery(ctx, query, *evaluationProvider) + value, _, err := provider.EvaluateQuery(ctx, query, *evaluationProvider) statusItem := &klcv1alpha2.EvaluationStatusItem{ Value: value, Status: apicommon.StateFailed, diff --git a/operator/controllers/metrics/keptnmetric_controller.go b/operator/controllers/metrics/keptnmetric_controller.go index bb26d4de39..a315847568 100644 --- a/operator/controllers/metrics/keptnmetric_controller.go +++ b/operator/controllers/metrics/keptnmetric_controller.go @@ -18,15 +18,24 @@ package metrics import ( "context" + "time" "github.com/go-logr/logr" + klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/metrics/v1alpha1" + "github.com/keptn/lifecycle-toolkit/operator/controllers/common/providers" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/predicate" ) +const MB = 1 << (10 * 2) + // KeptnMetricReconciler reconciles a KeptnMetric object type KeptnMetricReconciler struct { client.Client @@ -34,10 +43,15 @@ type KeptnMetricReconciler struct { Log logr.Logger } -//+kubebuilder:rbac:groups=metrics.keptn.sh,resources=keptnmetrics,verbs=get;list;watch;create;update;patch;delete +//clusterrole +//+kubebuilder:rbac:groups=metrics.keptn.sh,resources=providers,verbs=get;list;watch +//+kubebuilder:rbac:groups=metrics.keptn.sh,resources=keptnmetrics,verbs=get;list;watch; //+kubebuilder:rbac:groups=metrics.keptn.sh,resources=keptnmetrics/status,verbs=get;update;patch //+kubebuilder:rbac:groups=metrics.keptn.sh,resources=keptnmetrics/finalizers,verbs=update +//role +//+kubebuilder:rbac:groups=core,namespace=keptn-lifecycle-toolkit-system,resources=secrets,verbs=get + // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by @@ -61,12 +75,67 @@ func (r *KeptnMetricReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } - return ctrl.Result{}, nil + fetchTime := metric.Status.LastUpdated.Add(time.Second * time.Duration(metric.Spec.FetchIntervalSeconds)) + if time.Now().Before(fetchTime) { + r.Log.Info("Metric has not been updated for the configured interval. Skipping") + return ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil + } + + evaluationProvider, err := r.fetchProvider(ctx, types.NamespacedName{Name: metric.Spec.Provider.Name, Namespace: metric.Namespace}) + if err != nil { + if errors.IsNotFound(err) { + r.Log.Info(err.Error() + ", ignoring error since object must be deleted") + return ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil + } + r.Log.Error(err, "Failed to retrieve the provider") + return ctrl.Result{}, nil + } + // load the provider + provider, err2 := providers.NewProvider(metric.Spec.Provider.Name, r.Log, r.Client) + if err2 != nil { + r.Log.Error(err2, "Failed to get the correct Metric Provider") + return ctrl.Result{Requeue: false}, err2 + } + + objective := klcv1alpha2.Objective{ + Name: metric.Name, + Query: metric.Spec.Query, + } + value, rawValue, err := provider.EvaluateQuery(ctx, objective, *evaluationProvider) + if err != nil { + r.Log.Error(err, "Failed to evaluate the query") + return ctrl.Result{Requeue: false}, err + } + metric.Status.Value = value + metric.Status.RawValue = cupSize(rawValue) + metric.Status.LastUpdated = metav1.Time{Time: time.Now()} + + if err := r.Client.Status().Update(ctx, metric); err != nil { + r.Log.Error(err, "Failed to update the Metric status") + return ctrl.Result{}, err + } + + return ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil +} + +func cupSize(value []byte) []byte { + if len(value) > MB { + return value[:MB] + } + return value } // SetupWithManager sets up the controller with the Manager. func (r *KeptnMetricReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&metricsv1alpha1.KeptnMetric{}). + For(&metricsv1alpha1.KeptnMetric{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) } + +func (r *KeptnMetricReconciler) fetchProvider(ctx context.Context, namespacedMetric types.NamespacedName) (*klcv1alpha2.KeptnEvaluationProvider, error) { + provider := &klcv1alpha2.KeptnEvaluationProvider{} + if err := r.Client.Get(ctx, namespacedMetric, provider); err != nil { + return nil, err + } + return provider, nil +} diff --git a/operator/controllers/metrics/keptnmetric_controller_test.go b/operator/controllers/metrics/keptnmetric_controller_test.go new file mode 100644 index 0000000000..2812fb93a6 --- /dev/null +++ b/operator/controllers/metrics/keptnmetric_controller_test.go @@ -0,0 +1,239 @@ +package metrics + +import ( + "context" + "fmt" + "reflect" + "testing" + "time" + + "github.com/go-logr/logr/testr" + klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2" + metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/metrics/v1alpha1" + "github.com/keptn/lifecycle-toolkit/operator/controllers/common/fake" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + controllerruntime "sigs.k8s.io/controller-runtime" +) + +func TestKeptnMetricReconciler_fetchProvider(t *testing.T) { + provider := klcv1alpha2.KeptnEvaluationProvider{ + TypeMeta: metav1.TypeMeta{ + Kind: "KeptnEvaluationProvider", + APIVersion: "lifecycle.keptn.sh/v1alpha2"}, + ObjectMeta: metav1.ObjectMeta{ + Name: "myprovider", + Namespace: "default", + }, + Spec: klcv1alpha2.KeptnEvaluationProviderSpec{}, + Status: klcv1alpha2.KeptnEvaluationProviderStatus{}, + } + + client := fake.NewClient(&provider) + r := &KeptnMetricReconciler{ + Client: client, + Scheme: client.Scheme(), + Log: testr.New(t), + } + + // fetch existing provider based on source + namespacedProvider := types.NamespacedName{Namespace: "default", Name: "myprovider"} + got, err := r.fetchProvider(context.TODO(), namespacedProvider) + require.Nil(t, err) + require.Equal(t, provider, *got) + + //fetch unexisting provider + + namespacedProvider2 := types.NamespacedName{Namespace: "default", Name: "myunexistingprovider"} + got, err = r.fetchProvider(context.TODO(), namespacedProvider2) + require.Error(t, err) + require.True(t, errors.IsNotFound(err)) + require.Nil(t, got) +} + +func TestKeptnMetricReconciler_Reconcile(t *testing.T) { + + metric := &metricsv1alpha1.KeptnMetric{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mymetric", + Namespace: "default", + }, + Spec: metricsv1alpha1.KeptnMetricSpec{ + Provider: metricsv1alpha1.ProviderRef{}, + Query: "", + FetchIntervalSeconds: 1, + }, + Status: metricsv1alpha1.KeptnMetricStatus{ + Value: "12", + RawValue: nil, + LastUpdated: metav1.Time{Time: time.Now().Add(-1 * time.Minute)}, + }, + } + metric2 := &metricsv1alpha1.KeptnMetric{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mymetric2", + Namespace: "default", + }, + Spec: metricsv1alpha1.KeptnMetricSpec{ + Provider: metricsv1alpha1.ProviderRef{}, + Query: "", + FetchIntervalSeconds: 1, + }, + Status: metricsv1alpha1.KeptnMetricStatus{ + Value: "12", + RawValue: nil, + LastUpdated: metav1.Time{Time: time.Now().Add(-1 * time.Minute)}, + }, + } + + metric3 := &metricsv1alpha1.KeptnMetric{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mymetric3", + Namespace: "default", + }, + Spec: metricsv1alpha1.KeptnMetricSpec{ + Provider: metricsv1alpha1.ProviderRef{ + Name: "myprov", + }, + Query: "", + FetchIntervalSeconds: 10, + }, + Status: metricsv1alpha1.KeptnMetricStatus{ + Value: "12", + RawValue: nil, + LastUpdated: metav1.Time{Time: time.Now().Add(-1 * time.Minute)}, + }, + } + + metric4 := &metricsv1alpha1.KeptnMetric{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mymetric4", + Namespace: "default", + }, + Spec: metricsv1alpha1.KeptnMetricSpec{ + Provider: metricsv1alpha1.ProviderRef{ + Name: "prometheus", + }, + Query: "", + FetchIntervalSeconds: 10, + }, + Status: metricsv1alpha1.KeptnMetricStatus{ + Value: "12", + RawValue: nil, + LastUpdated: metav1.Time{Time: time.Now().Add(-1 * time.Minute)}, + }, + } + + provider := &klcv1alpha2.KeptnEvaluationProvider{ + ObjectMeta: metav1.ObjectMeta{Name: "myprov", Namespace: "default"}, + Spec: klcv1alpha2.KeptnEvaluationProviderSpec{}, + Status: klcv1alpha2.KeptnEvaluationProviderStatus{}, + } + + supportedprov := &klcv1alpha2.KeptnEvaluationProvider{ + ObjectMeta: metav1.ObjectMeta{ + Name: "prometheus", + Namespace: "default", + }, + Spec: klcv1alpha2.KeptnEvaluationProviderSpec{ + TargetServer: "http://keptn.sh", + }, + Status: klcv1alpha2.KeptnEvaluationProviderStatus{}, + } + + client := fake.NewClient(metric, metric2, metric3, metric4, provider, supportedprov) + + r := &KeptnMetricReconciler{ + Client: client, + Scheme: client.Scheme(), + Log: testr.New(t), + } + + tests := []struct { + name string + ctx context.Context + req controllerruntime.Request + want controllerruntime.Result + wantErr error + }{ + { + name: "metric not found, ignoring", + ctx: context.TODO(), + req: controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "myunexistingmetric"}, + }, + want: controllerruntime.Result{}, + }, + + { + name: "metric exists, not time to fetch", + ctx: context.TODO(), + req: controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "mymetric"}, + }, + want: controllerruntime.Result{Requeue: true, RequeueAfter: 10 * time.Second}, + }, + + { + name: "metric exists, needs to fetch, provider not found ignoring", + ctx: context.TODO(), + req: controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "mymetric2"}, + }, + want: controllerruntime.Result{Requeue: true, RequeueAfter: 10 * time.Second}, + }, + + { + name: "metric exists, needs to fetch, provider unsupported", + ctx: context.TODO(), + req: controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "mymetric3"}, + }, + want: controllerruntime.Result{Requeue: false, RequeueAfter: 0}, + wantErr: fmt.Errorf("provider myprov not supported"), + }, + + { + name: "metric exists, needs to fetch, prometheus supported, bad query", + ctx: context.TODO(), + req: controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "mymetric4"}, + }, + want: controllerruntime.Result{Requeue: false, RequeueAfter: 0}, + wantErr: fmt.Errorf("client_error: client error: 404"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Log(tt.name) + got, err := r.Reconcile(tt.ctx, tt.req) + if tt.wantErr != nil { + require.NotNil(t, err) + require.Contains(t, err.Error(), tt.wantErr.Error()) + } else { + require.Nil(t, err) + } + + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reconcile() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_cupSize(t *testing.T) { + myVeryBigSlice := make([]byte, MB+1) + mySmallSlice := []byte("I am small") + myAtLimitSlice := make([]byte, MB) + + res1 := cupSize(myVeryBigSlice) + res2 := cupSize(mySmallSlice) + res3 := cupSize(myAtLimitSlice) + + require.Equal(t, len(res1), MB) + require.Equal(t, len(res2), len(mySmallSlice)) + require.Equal(t, len(res3), MB) + +} diff --git a/operator/controllers/metrics/suite_test.go b/operator/controllers/metrics/suite_test.go deleted file mode 100644 index d0ac9514d6..0000000000 --- a/operator/controllers/metrics/suite_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2022. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -import ( - "path/filepath" - "testing" - - metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/metrics/v1alpha1" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - //+kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment - -func TestAPIs(t *testing.T) { - // RegisterFailHandler(Fail) - - // RunSpecsWithDefaultAndCustomReporters(t, - // "Controller Suite", - // []Reporter{printer.NewlineReporter{}}) -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - var err error - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - err = metricsv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - //+kubebuilder:scaffold:scheme - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) - -}, 60) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) diff --git a/operator/go.mod b/operator/go.mod index 3675387a5e..9abe0568f4 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -8,7 +8,6 @@ require ( github.com/imdario/mergo v0.3.13 github.com/kelseyhightower/envconfig v1.4.0 github.com/magiconair/properties v1.8.7 - github.com/onsi/ginkgo v1.16.5 github.com/onsi/ginkgo/v2 v2.7.0 github.com/onsi/gomega v1.24.2 github.com/pkg/errors v0.9.1 @@ -65,7 +64,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nxadm/tail v1.4.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect @@ -87,7 +85,6 @@ require ( google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/component-base v0.25.5 // indirect diff --git a/operator/go.sum b/operator/go.sum index 109a78f155..fcdddca29b 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -82,8 +82,6 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -107,7 +105,6 @@ github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -182,7 +179,6 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= @@ -223,17 +219,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.24.2 h1:J/tulyYK6JwBldPViHJReihxxZ+22FHs0piGjQAvoUE= github.com/onsi/gomega v1.24.2/go.mod h1:gs3J10IS7Z7r7eXRoNJIrNqU4ToQukCJhFtKrWgHWnk= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -356,7 +345,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-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= @@ -378,7 +366,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/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-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -406,7 +393,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -415,10 +401,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -436,7 +419,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -501,7 +483,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -609,16 +590,12 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=