diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index 0fec4f061..50ce0666d 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -16,7 +16,7 @@ import ( "github.com/traefik/traefik/v2/pkg/version" ) -// collectorURL URL where the stats are send. +// collectorURL URL where the stats are sent. const collectorURL = "https://collect.traefik.io/9vxmmkcdmalbdi635d4jgc5p5rx0h7h8" // Collected data. @@ -30,16 +30,30 @@ type data struct { // Collect anonymous data. func Collect(staticConfiguration *static.Configuration) error { - anonConfig, err := redactor.Anonymize(staticConfiguration) + buf, err := createBody(staticConfiguration) if err != nil { return err } + resp, err := makeHTTPClient().Post(collectorURL, "application/json; charset=utf-8", buf) + if resp != nil { + _ = resp.Body.Close() + } + + return err +} + +func createBody(staticConfiguration *static.Configuration) (*bytes.Buffer, error) { + anonConfig, err := redactor.Anonymize(staticConfiguration) + if err != nil { + return nil, err + } + log.WithoutContext().Infof("Anonymous stats sent to %s: %s", collectorURL, anonConfig) hashConf, err := hashstructure.Hash(staticConfiguration, nil) if err != nil { - return err + return nil, err } data := &data{ @@ -53,15 +67,10 @@ func Collect(staticConfiguration *static.Configuration) error { buf := new(bytes.Buffer) err = json.NewEncoder(buf).Encode(data) if err != nil { - return err + return nil, err } - resp, err := makeHTTPClient().Post(collectorURL, "application/json; charset=utf-8", buf) - if resp != nil { - resp.Body.Close() - } - - return err + return buf, err } func makeHTTPClient() *http.Client { diff --git a/pkg/collector/collector_test.go b/pkg/collector/collector_test.go new file mode 100644 index 000000000..b091f0c4f --- /dev/null +++ b/pkg/collector/collector_test.go @@ -0,0 +1,21 @@ +package collector + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/config/static" +) + +func Test_createBody(t *testing.T) { + var staticConfiguration static.Configuration + + err := hydrate(&staticConfiguration) + require.NoError(t, err) + + buffer, err := createBody(&staticConfiguration) + require.NoError(t, err) + + assert.NotEmpty(t, buffer) +} diff --git a/pkg/collector/hydration_test.go b/pkg/collector/hydration_test.go new file mode 100644 index 000000000..8814888a6 --- /dev/null +++ b/pkg/collector/hydration_test.go @@ -0,0 +1,166 @@ +package collector + +import ( + "fmt" + "reflect" + "time" + + "github.com/traefik/paerser/types" +) + +const ( + sliceItemNumber = 2 + mapItemNumber = 2 + defaultString = "foobar" + defaultNumber = 42 + defaultBool = true + defaultMapKeyPrefix = "name" +) + +func hydrate(element interface{}) error { + field := reflect.ValueOf(element) + return fill(field) +} + +func fill(field reflect.Value) error { + switch field.Kind() { + case reflect.Struct: + if err := setStruct(field); err != nil { + return err + } + case reflect.Ptr: + if err := setPointer(field); err != nil { + return err + } + case reflect.Slice: + if err := setSlice(field); err != nil { + return err + } + case reflect.Map: + if err := setMap(field); err != nil { + return err + } + case reflect.Interface: + if err := fill(field.Elem()); err != nil { + return err + } + case reflect.String: + setTyped(field, defaultString) + case reflect.Int: + setTyped(field, defaultNumber) + case reflect.Int8: + setTyped(field, int8(defaultNumber)) + case reflect.Int16: + setTyped(field, int16(defaultNumber)) + case reflect.Int32: + setTyped(field, int32(defaultNumber)) + case reflect.Int64: + switch field.Type() { + case reflect.TypeOf(types.Duration(time.Second)): + setTyped(field, int64(defaultNumber*int(time.Second))) + default: + setTyped(field, int64(defaultNumber)) + } + case reflect.Uint: + setTyped(field, uint(defaultNumber)) + case reflect.Uint8: + setTyped(field, uint8(defaultNumber)) + case reflect.Uint16: + setTyped(field, uint16(defaultNumber)) + case reflect.Uint32: + setTyped(field, uint32(defaultNumber)) + case reflect.Uint64: + setTyped(field, uint64(defaultNumber)) + case reflect.Bool: + setTyped(field, defaultBool) + case reflect.Float32: + setTyped(field, float32(defaultNumber)) + case reflect.Float64: + setTyped(field, float64(defaultNumber)) + } + + return nil +} + +func setTyped(field reflect.Value, i interface{}) { + baseValue := reflect.ValueOf(i) + if field.Kind().String() == field.Type().String() { + field.Set(baseValue) + } else { + field.Set(baseValue.Convert(field.Type())) + } +} + +func setMap(field reflect.Value) error { + field.Set(reflect.MakeMap(field.Type())) + + for i := 0; i < mapItemNumber; i++ { + baseKeyName := makeKeyName(field.Type().Elem()) + key := reflect.ValueOf(fmt.Sprintf("%s%d", baseKeyName, i)) + + // generate value + ptrType := reflect.PtrTo(field.Type().Elem()) + ptrValue := reflect.New(ptrType) + if err := fill(ptrValue); err != nil { + return err + } + value := ptrValue.Elem().Elem() + + field.SetMapIndex(key, value) + } + return nil +} + +func makeKeyName(typ reflect.Type) string { + switch typ.Kind() { + case reflect.Ptr: + return typ.Elem().Name() + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Bool, reflect.Float32, reflect.Float64: + return defaultMapKeyPrefix + default: + return typ.Name() + } +} + +func setStruct(field reflect.Value) error { + for i := 0; i < field.NumField(); i++ { + fld := field.Field(i) + stFld := field.Type().Field(i) + + if !stFld.IsExported() || fld.Kind() == reflect.Func { + continue + } + + if err := fill(fld); err != nil { + return err + } + } + return nil +} + +func setSlice(field reflect.Value) error { + field.Set(reflect.MakeSlice(field.Type(), sliceItemNumber, sliceItemNumber)) + for j := 0; j < field.Len(); j++ { + if err := fill(field.Index(j)); err != nil { + return err + } + } + return nil +} + +func setPointer(field reflect.Value) error { + if field.IsNil() { + field.Set(reflect.New(field.Type().Elem())) + if err := fill(field.Elem()); err != nil { + return err + } + } else { + if err := fill(field.Elem()); err != nil { + return err + } + } + return nil +}