From 8415b375689be66a4eee5f75b66d071aaa4d7599 Mon Sep 17 00:00:00 2001 From: Gerard Vanloo Date: Thu, 27 May 2021 11:30:24 -0400 Subject: [PATCH] Creating telemetry metrics for loki operator based on complete build spec (#32) --- go.mod | 4 +- .../handlers/lokistack_create_or_update.go | 14 ++ internal/manifests/build.go | 28 ++- internal/manifests/build_test.go | 30 +-- internal/metrics/metrics.go | 161 +++++++++++++++ main.go | 4 + vendor/github.com/creasty/defaults/.gitignore | 1 - .../github.com/creasty/defaults/.travis.yml | 20 -- vendor/github.com/creasty/defaults/LICENSE | 22 -- vendor/github.com/creasty/defaults/Makefile | 30 --- vendor/github.com/creasty/defaults/README.md | 69 ------- .../github.com/creasty/defaults/defaults.go | 195 ------------------ vendor/github.com/creasty/defaults/go.mod | 3 - vendor/github.com/creasty/defaults/setter.go | 12 -- vendor/modules.txt | 3 +- 15 files changed, 216 insertions(+), 380 deletions(-) create mode 100644 internal/metrics/metrics.go delete mode 100644 vendor/github.com/creasty/defaults/.gitignore delete mode 100644 vendor/github.com/creasty/defaults/.travis.yml delete mode 100644 vendor/github.com/creasty/defaults/LICENSE delete mode 100644 vendor/github.com/creasty/defaults/Makefile delete mode 100644 vendor/github.com/creasty/defaults/README.md delete mode 100644 vendor/github.com/creasty/defaults/defaults.go delete mode 100644 vendor/github.com/creasty/defaults/go.mod delete mode 100644 vendor/github.com/creasty/defaults/setter.go diff --git a/go.mod b/go.mod index f098a01dcc..8c46b91997 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.16 require ( github.com/ViaQ/logerr v1.0.9 - github.com/creasty/defaults v1.5.1 + github.com/creasty/defaults v1.5.1 // indirect github.com/go-logr/logr v0.4.0 + github.com/google/uuid v1.1.2 github.com/imdario/mergo v0.3.10 github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 + github.com/prometheus/client_golang v1.7.1 github.com/stretchr/testify v1.6.1 k8s.io/api v0.20.4 k8s.io/apimachinery v0.20.4 diff --git a/internal/handlers/lokistack_create_or_update.go b/internal/handlers/lokistack_create_or_update.go index b6e115ee19..1e40508ade 100644 --- a/internal/handlers/lokistack_create_or_update.go +++ b/internal/handlers/lokistack_create_or_update.go @@ -12,6 +12,7 @@ import ( "github.com/ViaQ/loki-operator/internal/external/k8s" "github.com/ViaQ/loki-operator/internal/handlers/internal/secrets" "github.com/ViaQ/loki-operator/internal/manifests" + "github.com/ViaQ/loki-operator/internal/metrics" "github.com/ViaQ/loki-operator/internal/status" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -71,6 +72,11 @@ func CreateOrUpdateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client ll.Info("begin building manifests") + if optErr := manifests.ApplyDefaultSettings(&opts); optErr != nil { + ll.Error(optErr, "failed to conform options to build settings") + return optErr + } + objects, err := manifests.BuildAll(opts) if err != nil { ll.Error(err, "failed to build manifests") @@ -79,6 +85,7 @@ func CreateOrUpdateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client ll.Info("manifests built", "count", len(objects)) var errCount int32 + for _, obj := range objects { l := ll.WithValues( "object_name", obj.GetName(), @@ -102,6 +109,7 @@ func CreateOrUpdateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client errCount++ continue } + l.Info(fmt.Sprintf("Resource has been %s", op)) } @@ -109,5 +117,11 @@ func CreateOrUpdateLokiStack(ctx context.Context, req ctrl.Request, k k8s.Client return kverrors.New("failed to configure lokistack resources", "name", req.NamespacedName) } + // 1x.extra-small is used only for development, so the metrics will not + // be collected. + if opts.Stack.Size != lokiv1beta1.SizeOneXExtraSmall { + metrics.Collect(&opts.Stack, opts.Name) + } + return nil } diff --git a/internal/manifests/build.go b/internal/manifests/build.go index 62464ac0ef..53a1fb5513 100644 --- a/internal/manifests/build.go +++ b/internal/manifests/build.go @@ -13,11 +13,6 @@ import ( func BuildAll(opt Options) ([]client.Object, error) { res := make([]client.Object, 0) - opt, err := applyUserOptions(opt) - if err != nil { - return nil, err - } - cm, sha1C, err := LokiConfigMap(opt) if err != nil { return nil, err @@ -35,11 +30,20 @@ func BuildAll(opt Options) ([]client.Object, error) { return res, nil } -func applyUserOptions(opt Options) (Options, error) { - defs := internal.StackSizeTable[opt.Stack.Size] - spec := (&defs).DeepCopy() +// DefaultLokiStackSpec returns the default configuration for a LokiStack of +// the specified size +func DefaultLokiStackSpec(size lokiv1beta1.LokiStackSizeType) *lokiv1beta1.LokiStackSpec { + defaults := internal.StackSizeTable[size] + return (&defaults).DeepCopy() +} + +// ApplyDefaultSettings manipulates the options to conform to +// build specifications +func ApplyDefaultSettings(opt *Options) error { + spec := DefaultLokiStackSpec(opt.Stack.Size) + if err := mergo.Merge(spec, opt.Stack, mergo.WithOverride); err != nil { - return Options{}, kverrors.Wrap(err, "failed merging stack user options", "name", opt.Name) + return kverrors.Wrap(err, "failed merging stack user options", "name", opt.Name) } strictOverrides := lokiv1beta1.LokiStackSpec{ @@ -51,11 +55,13 @@ func applyUserOptions(opt Options) (Options, error) { }, }, } + if err := mergo.Merge(spec, strictOverrides, mergo.WithOverride); err != nil { - return Options{}, kverrors.Wrap(err, "failed to merge strict defaults") + return kverrors.Wrap(err, "failed to merge strict defaults") } opt.ResourceRequirements = internal.ResourceRequirementsTable[opt.Stack.Size] opt.Stack = *spec - return opt, nil + + return nil } diff --git a/internal/manifests/build_test.go b/internal/manifests/build_test.go index c21f7b20f1..a315f6e8e1 100644 --- a/internal/manifests/build_test.go +++ b/internal/manifests/build_test.go @@ -15,7 +15,7 @@ func TestApplyUserOptions_OverrideDefaults(t *testing.T) { lokiv1beta1.SizeOneXMedium, } for _, size := range allSizes { - in := Options{ + opt := Options{ Name: "abcd", Namespace: "efgh", Stack: lokiv1beta1.LokiStackSpec{ @@ -27,24 +27,24 @@ func TestApplyUserOptions_OverrideDefaults(t *testing.T) { }, }, } - out, err := applyUserOptions(in) + err := ApplyDefaultSettings(&opt) defs := internal.StackSizeTable[size] require.NoError(t, err) - require.Equal(t, defs.Size, out.Stack.Size) - require.Equal(t, defs.Limits, out.Stack.Limits) - require.Equal(t, defs.ReplicationFactor, out.Stack.ReplicationFactor) - require.Equal(t, defs.ManagementState, out.Stack.ManagementState) - require.Equal(t, defs.Template.Ingester, out.Stack.Template.Ingester) - require.Equal(t, defs.Template.Querier, out.Stack.Template.Querier) - require.Equal(t, defs.Template.QueryFrontend, out.Stack.Template.QueryFrontend) + require.Equal(t, defs.Size, opt.Stack.Size) + require.Equal(t, defs.Limits, opt.Stack.Limits) + require.Equal(t, defs.ReplicationFactor, opt.Stack.ReplicationFactor) + require.Equal(t, defs.ManagementState, opt.Stack.ManagementState) + require.Equal(t, defs.Template.Ingester, opt.Stack.Template.Ingester) + require.Equal(t, defs.Template.Querier, opt.Stack.Template.Querier) + require.Equal(t, defs.Template.QueryFrontend, opt.Stack.Template.QueryFrontend) // Require distributor replicas to be set by user overwrite - require.NotEqual(t, defs.Template.Distributor.Replicas, out.Stack.Template.Distributor.Replicas) + require.NotEqual(t, defs.Template.Distributor.Replicas, opt.Stack.Template.Distributor.Replicas) // Require distributor tolerations and nodeselectors to use defaults - require.Equal(t, defs.Template.Distributor.Tolerations, out.Stack.Template.Distributor.Tolerations) - require.Equal(t, defs.Template.Distributor.NodeSelector, out.Stack.Template.Distributor.NodeSelector) + require.Equal(t, defs.Template.Distributor.Tolerations, opt.Stack.Template.Distributor.Tolerations) + require.Equal(t, defs.Template.Distributor.NodeSelector, opt.Stack.Template.Distributor.NodeSelector) } } @@ -55,7 +55,7 @@ func TestApplyUserOptions_AlwaysSetCompactorReplicasToOne(t *testing.T) { lokiv1beta1.SizeOneXMedium, } for _, size := range allSizes { - in := Options{ + opt := Options{ Name: "abcd", Namespace: "efgh", Stack: lokiv1beta1.LokiStackSpec{ @@ -67,12 +67,12 @@ func TestApplyUserOptions_AlwaysSetCompactorReplicasToOne(t *testing.T) { }, }, } - out, err := applyUserOptions(in) + err := ApplyDefaultSettings(&opt) defs := internal.StackSizeTable[size] require.NoError(t, err) // Require compactor to be reverted to 1 replica - require.Equal(t, defs.Template.Compactor, out.Stack.Template.Compactor) + require.Equal(t, defs.Template.Compactor, opt.Stack.Template.Compactor) } } diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go new file mode 100644 index 0000000000..d6261eadaf --- /dev/null +++ b/internal/metrics/metrics.go @@ -0,0 +1,161 @@ +package metrics + +import ( + "reflect" + + "github.com/prometheus/client_golang/prometheus" + "sigs.k8s.io/controller-runtime/pkg/metrics" + + lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1" + "github.com/ViaQ/loki-operator/internal/manifests" +) + +// UserDefinedLimitsType defines a label that describes the type of limits +// imposed on the cluster +type UserDefinedLimitsType string + +const ( + labelGlobal UserDefinedLimitsType = "global" + labelTenant UserDefinedLimitsType = "tenant" +) + +var ( + deploymentMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "lokistack_deployments", + Help: "Number of clusters that are deployed", + }, + []string{"size", "stack_id"}, + ) + + userDefinedLimitsMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "lokistack_user_defined_limits", + Help: "Number of clusters that are using user defined limits", + }, + []string{"size", "stack_id", "type"}, + ) + + globalStreamLimitMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "lokistack_global_stream_limit", + Help: "Sum of stream limits used globally by the ingesters", + }, + []string{"size", "stack_id"}, + ) + + averageTenantStreamLimitMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "lokistack_avg_stream_limit_per_tenant", + Help: "Sum of stream limits used for defined tenants by the ingesters", + }, + []string{"size", "stack_id"}, + ) +) + +// RegisterMetricCollectors registers the prometheus collectors with the k8 default metrics +func RegisterMetricCollectors() { + metricCollectors := []prometheus.Collector{ + deploymentMetric, + userDefinedLimitsMetric, + globalStreamLimitMetric, + averageTenantStreamLimitMetric, + } + + for _, collector := range metricCollectors { + metrics.Registry.MustRegister(collector) + } +} + +// Collect takes metrics based on the spec +func Collect(spec *lokiv1beta1.LokiStackSpec, stackName string) { + defaultSpec := manifests.DefaultLokiStackSpec(spec.Size) + sizes := []lokiv1beta1.LokiStackSizeType{lokiv1beta1.SizeOneXSmall, lokiv1beta1.SizeOneXMedium} + + for _, size := range sizes { + var ( + globalRate float64 = 0 + tenantRate float64 = 0 + isUsingSize = false + isUsingTenantLimits = false + isUsingCustomGlobalLimits = false + ) + + if spec.Size == size { + isUsingSize = true + + if !reflect.DeepEqual(spec.Limits.Global, defaultSpec.Limits.Global) { + isUsingCustomGlobalLimits = true + } + + if len(spec.Limits.Tenants) != 0 { + isUsingTenantLimits = true + } + + if ingesters := spec.Template.Ingester.Replicas; ingesters > 0 { + tenantRate = streamRate(spec.Limits.Tenants, ingesters) + globalRate = float64(spec.Limits.Global.IngestionLimits.MaxGlobalStreamsPerTenant / ingesters) + } + } + + setDeploymentMetric(size, stackName, isUsingSize) + setUserDefinedLimitsMetric(size, stackName, labelGlobal, isUsingCustomGlobalLimits) + setUserDefinedLimitsMetric(size, stackName, labelTenant, isUsingTenantLimits) + setGlobalStreamLimitMetric(size, stackName, globalRate) + setAverageTenantStreamLimitMetric(size, stackName, tenantRate) + } +} + +func setDeploymentMetric(size lokiv1beta1.LokiStackSizeType, identifier string, active bool) { + deploymentMetric.With(prometheus.Labels{ + "size": string(size), + "stack_id": identifier, + }).Set(boolValue(active)) +} + +func setUserDefinedLimitsMetric(size lokiv1beta1.LokiStackSizeType, identifier string, limitType UserDefinedLimitsType, active bool) { + userDefinedLimitsMetric.With(prometheus.Labels{ + "size": string(size), + "stack_id": identifier, + "type": string(limitType), + }).Set(boolValue(active)) +} + +func setGlobalStreamLimitMetric(size lokiv1beta1.LokiStackSizeType, identifier string, rate float64) { + globalStreamLimitMetric.With(prometheus.Labels{ + "size": string(size), + "stack_id": identifier, + }).Set(rate) +} + +func setAverageTenantStreamLimitMetric(size lokiv1beta1.LokiStackSizeType, identifier string, rate float64) { + averageTenantStreamLimitMetric.With(prometheus.Labels{ + "size": string(size), + "stack_id": identifier, + }).Set(rate) +} + +func boolValue(value bool) float64 { + if value { + return 1 + } + return 0 +} + +func streamRate(tenantLimits map[string]lokiv1beta1.LimitsTemplateSpec, ingesters int32) float64 { + var tenants, tenantStreamLimit int32 = 0, 0 + + for _, tenant := range tenantLimits { + if tenant.IngestionLimits == nil || tenant.IngestionLimits.MaxGlobalStreamsPerTenant == 0 { + continue + } + + tenants++ + tenantStreamLimit += tenant.IngestionLimits.MaxGlobalStreamsPerTenant + } + + if tenants == 0 || ingesters == 0 { + return 0 + } + return float64(tenantStreamLimit / ingesters / tenants) +} diff --git a/main.go b/main.go index 18d5b5f807..3078c08b98 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,7 @@ import ( lokiv1beta1 "github.com/ViaQ/loki-operator/api/v1beta1" "github.com/ViaQ/loki-operator/controllers" + "github.com/ViaQ/loki-operator/internal/metrics" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -90,6 +91,9 @@ func main() { os.Exit(1) } + log.Info("registering metrics") + metrics.RegisterMetricCollectors() + log.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { log.Error(err, "problem running manager") diff --git a/vendor/github.com/creasty/defaults/.gitignore b/vendor/github.com/creasty/defaults/.gitignore deleted file mode 100644 index e43b0f9889..0000000000 --- a/vendor/github.com/creasty/defaults/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/vendor/github.com/creasty/defaults/.travis.yml b/vendor/github.com/creasty/defaults/.travis.yml deleted file mode 100644 index b54679533d..0000000000 --- a/vendor/github.com/creasty/defaults/.travis.yml +++ /dev/null @@ -1,20 +0,0 @@ -language: go -go: 1.13 - -branches: - only: - - master - -env: - global: - # CODECOV_TOKEN - - secure: "O4Hay0TPFW2eq1+Y9vlW4W8VIY9QlKzGgEoL8mgbHQ67RrvqaB7Ft6kdmUaVztu9a5ZB4QwwU8dCFV6D/+dY710qqayQghY2r4igqExzkMyRa3InQtGjhYd9CVpjbR5cXYV1PL9h/Jf3PTTUCmSJp0tpqZCce4BezgnhdSjccGzx3wlzj66+bDdpCzRkWtCvYvI4Fxg5aM5kBke1Ti+aLdEQNzDpkbM38/iyUQDuM+y6ZO+AuP9zqdMY82O6yEMXJppPtVnfqhTJQyDiEF6J6h3TauOb9nKhu8Eg0d2b0HseTRVcCigH3usCm6WYmqhInmsbdAge9gSs9SdYq06VYghG/AvnuRNbmGn3DHuRelyH0gBuXrzZxNP9nfPego4bvk6jQvPSu/flB7JHixkDkFBCO1b2R3ZdOdT6fg+qPQBMxEGy3YJ6ylZ0oqC0AByC+0OP7Hc/xv5U4uqnJ5oBVt4yt26sEjlMs8piXnkoNDoXVjgjfpqLLMyuSRzL1nbf7P270E7L0hDzWInObWlM7FuAl6ghb4j20jvx+4C5UKb8JJPMdbEOcDWZuJOnpThpcNIeP08Xks1wDJ5R01cxK2gja8Hmg+nF320bHybn3RRyOke7tuC3Egez2QaKEoQl9YmcepO+TENcsFk86v8Le68UHi8mJps5mm7iuJb2xyQ=" - -before_install: - - go get -u golang.org/x/lint/golint - -script: - - make ci-test - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/creasty/defaults/LICENSE b/vendor/github.com/creasty/defaults/LICENSE deleted file mode 100644 index 1483dd2d83..0000000000 --- a/vendor/github.com/creasty/defaults/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2017-present Yuki Iwanaga - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/creasty/defaults/Makefile b/vendor/github.com/creasty/defaults/Makefile deleted file mode 100644 index bd42d46f4b..0000000000 --- a/vendor/github.com/creasty/defaults/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -SHELL := /bin/bash -eu -o pipefail - -GO_TEST_FLAGS := -v - -PACKAGE_DIRS := $(shell go list ./... 2> /dev/null | grep -v /vendor/) -SRC_FILES := $(shell find . -name '*.go' -not -path './vendor/*') - - -# Tasks -#----------------------------------------------- -.PHONY: lint -lint: - @gofmt -e -d -s $(SRC_FILES) | awk '{ e = 1; print $0 } END { if (e) exit(1) }' - @echo $(SRC_FILES) | xargs -n1 golint -set_exit_status - @go vet $(PACKAGE_DIRS) - -.PHONY: test -test: lint - @go test $(GO_TEST_FLAGS) $(PACKAGE_DIRS) - -.PHONY: ci-test -ci-test: lint - @echo > coverage.txt - @for d in $(PACKAGE_DIRS); do \ - go test -coverprofile=profile.out -covermode=atomic -race -v $$d; \ - if [ -f profile.out ]; then \ - cat profile.out >> coverage.txt; \ - rm profile.out; \ - fi; \ - done diff --git a/vendor/github.com/creasty/defaults/README.md b/vendor/github.com/creasty/defaults/README.md deleted file mode 100644 index 918864ac2c..0000000000 --- a/vendor/github.com/creasty/defaults/README.md +++ /dev/null @@ -1,69 +0,0 @@ -defaults -======== - -[![Build Status](https://travis-ci.org/creasty/defaults.svg?branch=master)](https://travis-ci.org/creasty/defaults) -[![codecov](https://codecov.io/gh/creasty/defaults/branch/master/graph/badge.svg)](https://codecov.io/gh/creasty/defaults) -[![GitHub release](https://img.shields.io/github/release/creasty/defaults.svg)](https://github.com/creasty/defaults/releases) -[![License](https://img.shields.io/github/license/creasty/defaults.svg)](./LICENSE) - -Initialize structs with default values - -- Supports almost all kind of types - - Scalar types - - `int/8/16/32/64`, `uint/8/16/32/64`, `float32/64` - - `uintptr`, `bool`, `string` - - Complex types - - `map`, `slice`, `struct` - - Aliased types - - `time.Duration` - - e.g., `type Enum string` - - Pointer types - - e.g., `*SampleStruct`, `*int` -- Recursively initializes fields in a struct -- Dynamically sets default values by [`defaults.Setter`](./setter.go) interface -- Preserves non-initial values from being reset with a default value - - -Usage ------ - -```go -type Gender string - -type Sample struct { - Name string `default:"John Smith"` - Age int `default:"27"` - Gender Gender `default:"m"` - - Slice []string `default:"[]"` - SliceByJSON []int `default:"[1, 2, 3]"` // Supports JSON - Map map[string]int `default:"{}"` - MapByJSON map[string]int `default:"{\"foo\": 123}"` - - Struct OtherStruct `default:"{}"` - StructPtr *OtherStruct `default:"{\"Foo\": 123}"` - - NoTag OtherStruct // Recurses into a nested struct by default - OptOut OtherStruct `default:"-"` // Opt-out -} - -type OtherStruct struct { - Hello string `default:"world"` // Tags in a nested struct also work - Foo int `default:"-"` - Random int `default:"-"` -} - -// SetDefaults implements defaults.Setter interface -func (s *OtherStruct) SetDefaults() { - if defaults.CanUpdate(s.Random) { // Check if it's a zero value (recommended) - s.Random = rand.Int() // Set a dynamic value - } -} -``` - -```go -obj := &Sample{} -if err := defaults.Set(obj); err != nil { - panic(err) -} -``` diff --git a/vendor/github.com/creasty/defaults/defaults.go b/vendor/github.com/creasty/defaults/defaults.go deleted file mode 100644 index f35adf819e..0000000000 --- a/vendor/github.com/creasty/defaults/defaults.go +++ /dev/null @@ -1,195 +0,0 @@ -package defaults - -import ( - "encoding/json" - "errors" - "reflect" - "strconv" - "time" -) - -var ( - errInvalidType = errors.New("not a struct pointer") -) - -const ( - fieldName = "default" -) - -// Set initializes members in a struct referenced by a pointer. -// Maps and slices are initialized by `make` and other primitive types are set with default values. -// `ptr` should be a struct pointer -func Set(ptr interface{}) error { - if reflect.TypeOf(ptr).Kind() != reflect.Ptr { - return errInvalidType - } - - v := reflect.ValueOf(ptr).Elem() - t := v.Type() - - if t.Kind() != reflect.Struct { - return errInvalidType - } - - for i := 0; i < t.NumField(); i++ { - if defaultVal := t.Field(i).Tag.Get(fieldName); defaultVal != "-" { - if err := setField(v.Field(i), defaultVal); err != nil { - return err - } - } - } - callSetter(ptr) - return nil -} - -// MustSet function is a wrapper of Set function -// It will call Set and panic if err not equals nil. -func MustSet(ptr interface{}) { - if err := Set(ptr); err != nil { - panic(err) - } -} - -func setField(field reflect.Value, defaultVal string) error { - if !field.CanSet() { - return nil - } - - if !shouldInitializeField(field, defaultVal) { - return nil - } - - if isInitialValue(field) { - switch field.Kind() { - case reflect.Bool: - if val, err := strconv.ParseBool(defaultVal); err == nil { - field.Set(reflect.ValueOf(val).Convert(field.Type())) - } - case reflect.Int: - if val, err := strconv.ParseInt(defaultVal, 0, strconv.IntSize); err == nil { - field.Set(reflect.ValueOf(int(val)).Convert(field.Type())) - } - case reflect.Int8: - if val, err := strconv.ParseInt(defaultVal, 0, 8); err == nil { - field.Set(reflect.ValueOf(int8(val)).Convert(field.Type())) - } - case reflect.Int16: - if val, err := strconv.ParseInt(defaultVal, 0, 16); err == nil { - field.Set(reflect.ValueOf(int16(val)).Convert(field.Type())) - } - case reflect.Int32: - if val, err := strconv.ParseInt(defaultVal, 0, 32); err == nil { - field.Set(reflect.ValueOf(int32(val)).Convert(field.Type())) - } - case reflect.Int64: - if val, err := time.ParseDuration(defaultVal); err == nil { - field.Set(reflect.ValueOf(val).Convert(field.Type())) - } else if val, err := strconv.ParseInt(defaultVal, 0, 64); err == nil { - field.Set(reflect.ValueOf(val).Convert(field.Type())) - } - case reflect.Uint: - if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { - field.Set(reflect.ValueOf(uint(val)).Convert(field.Type())) - } - case reflect.Uint8: - if val, err := strconv.ParseUint(defaultVal, 0, 8); err == nil { - field.Set(reflect.ValueOf(uint8(val)).Convert(field.Type())) - } - case reflect.Uint16: - if val, err := strconv.ParseUint(defaultVal, 0, 16); err == nil { - field.Set(reflect.ValueOf(uint16(val)).Convert(field.Type())) - } - case reflect.Uint32: - if val, err := strconv.ParseUint(defaultVal, 0, 32); err == nil { - field.Set(reflect.ValueOf(uint32(val)).Convert(field.Type())) - } - case reflect.Uint64: - if val, err := strconv.ParseUint(defaultVal, 0, 64); err == nil { - field.Set(reflect.ValueOf(val).Convert(field.Type())) - } - case reflect.Uintptr: - if val, err := strconv.ParseUint(defaultVal, 0, strconv.IntSize); err == nil { - field.Set(reflect.ValueOf(uintptr(val)).Convert(field.Type())) - } - case reflect.Float32: - if val, err := strconv.ParseFloat(defaultVal, 32); err == nil { - field.Set(reflect.ValueOf(float32(val)).Convert(field.Type())) - } - case reflect.Float64: - if val, err := strconv.ParseFloat(defaultVal, 64); err == nil { - field.Set(reflect.ValueOf(val).Convert(field.Type())) - } - case reflect.String: - field.Set(reflect.ValueOf(defaultVal).Convert(field.Type())) - - case reflect.Slice: - ref := reflect.New(field.Type()) - ref.Elem().Set(reflect.MakeSlice(field.Type(), 0, 0)) - if defaultVal != "" && defaultVal != "[]" { - if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { - return err - } - } - field.Set(ref.Elem().Convert(field.Type())) - case reflect.Map: - ref := reflect.New(field.Type()) - ref.Elem().Set(reflect.MakeMap(field.Type())) - if defaultVal != "" && defaultVal != "{}" { - if err := json.Unmarshal([]byte(defaultVal), ref.Interface()); err != nil { - return err - } - } - field.Set(ref.Elem().Convert(field.Type())) - case reflect.Struct: - if defaultVal != "" && defaultVal != "{}" { - if err := json.Unmarshal([]byte(defaultVal), field.Addr().Interface()); err != nil { - return err - } - } - case reflect.Ptr: - field.Set(reflect.New(field.Type().Elem())) - } - } - - switch field.Kind() { - case reflect.Ptr: - setField(field.Elem(), defaultVal) - callSetter(field.Interface()) - case reflect.Struct: - if err := Set(field.Addr().Interface()); err != nil { - return err - } - case reflect.Slice: - for j := 0; j < field.Len(); j++ { - if err := setField(field.Index(j), defaultVal); err != nil { - return err - } - } - } - - return nil -} - -func isInitialValue(field reflect.Value) bool { - return reflect.DeepEqual(reflect.Zero(field.Type()).Interface(), field.Interface()) -} - -func shouldInitializeField(field reflect.Value, tag string) bool { - switch field.Kind() { - case reflect.Struct: - return true - case reflect.Ptr: - if !field.IsNil() && field.Elem().Kind() == reflect.Struct { - return true - } - case reflect.Slice: - return field.Len() > 0 || tag != "" - } - - return tag != "" -} - -// CanUpdate returns true when the given value is an initial value of its type -func CanUpdate(v interface{}) bool { - return isInitialValue(reflect.ValueOf(v)) -} diff --git a/vendor/github.com/creasty/defaults/go.mod b/vendor/github.com/creasty/defaults/go.mod deleted file mode 100644 index fae962c837..0000000000 --- a/vendor/github.com/creasty/defaults/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/creasty/defaults - -go 1.13 diff --git a/vendor/github.com/creasty/defaults/setter.go b/vendor/github.com/creasty/defaults/setter.go deleted file mode 100644 index 1f64aa6599..0000000000 --- a/vendor/github.com/creasty/defaults/setter.go +++ /dev/null @@ -1,12 +0,0 @@ -package defaults - -// Setter is an interface for setting default values -type Setter interface { - SetDefaults() -} - -func callSetter(v interface{}) { - if ds, ok := v.(Setter); ok { - ds.SetDefaults() - } -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6fa7e27aab..529a4e8c72 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -24,7 +24,6 @@ github.com/beorn7/perks/quantile github.com/cespare/xxhash/v2 # github.com/creasty/defaults v1.5.1 ## explicit -github.com/creasty/defaults # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/evanphx/json-patch v4.9.0+incompatible @@ -56,6 +55,7 @@ github.com/google/go-cmp/cmp/internal/value # github.com/google/gofuzz v1.1.0 github.com/google/gofuzz # github.com/google/uuid v1.1.2 +## explicit github.com/google/uuid # github.com/googleapis/gnostic v0.5.1 github.com/googleapis/gnostic/compiler @@ -102,6 +102,7 @@ github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib # github.com/prometheus/client_golang v1.7.1 +## explicit github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal github.com/prometheus/client_golang/prometheus/promhttp