Creating telemetry metrics for loki operator based on complete build spec (#32)

pull/4881/head
Gerard Vanloo 4 years ago committed by GitHub
parent 7045c92086
commit 8415b37568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      go.mod
  2. 14
      internal/handlers/lokistack_create_or_update.go
  3. 28
      internal/manifests/build.go
  4. 30
      internal/manifests/build_test.go
  5. 161
      internal/metrics/metrics.go
  6. 4
      main.go
  7. 1
      vendor/github.com/creasty/defaults/.gitignore
  8. 20
      vendor/github.com/creasty/defaults/.travis.yml
  9. 22
      vendor/github.com/creasty/defaults/LICENSE
  10. 30
      vendor/github.com/creasty/defaults/Makefile
  11. 69
      vendor/github.com/creasty/defaults/README.md
  12. 195
      vendor/github.com/creasty/defaults/defaults.go
  13. 3
      vendor/github.com/creasty/defaults/go.mod
  14. 12
      vendor/github.com/creasty/defaults/setter.go
  15. 3
      vendor/modules.txt

@ -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

@ -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
}

@ -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
}

@ -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)
}
}

@ -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)
}

@ -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")

@ -1 +0,0 @@
.DS_Store

@ -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)

@ -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.

@ -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

@ -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)
}
```

@ -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))
}

@ -1,3 +0,0 @@
module github.com/creasty/defaults
go 1.13

@ -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()
}
}

@ -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

Loading…
Cancel
Save