Compare commits
4 Commits
main
...
introduce-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a79764624 | ||
|
|
62c880faa0 | ||
|
|
d48425d236 | ||
|
|
efdede1e25 |
280
ee/flagger/licenseprovider/provider.go
Normal file
280
ee/flagger/licenseprovider/provider.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package licenseprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
config flagger.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
registry featuretypes.Registry
|
||||
licensing licensing.Licensing
|
||||
}
|
||||
|
||||
func NewFactory(registry featuretypes.Registry, licensing licensing.Licensing) factory.ProviderFactory[flagger.Provider, flagger.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("license"), func(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config) (flagger.Provider, error) {
|
||||
return New(ctx, providerSettings, config, registry, licensing)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config, registry featuretypes.Registry, licensing licensing.Licensing) (flagger.Provider, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/flagger/licenseprovider")
|
||||
|
||||
return &provider{
|
||||
config: config,
|
||||
settings: settings,
|
||||
registry: registry,
|
||||
licensing: licensing,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Metadata() openfeature.Metadata {
|
||||
return openfeature.Metadata{
|
||||
Name: "license",
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx["orgID"].(valuer.UUID))
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := license.FeatureVariants()[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[bool](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) StringEvaluation(ctx context.Context, flag string, defaultValue string, evalCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx["orgID"].(valuer.UUID))
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := license.FeatureVariants()[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[string](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx["orgID"].(valuer.UUID))
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := license.FeatureVariants()[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[float64](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, evalCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx["orgID"].(valuer.UUID))
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := license.FeatureVariants()[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[int64](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) ObjectEvaluation(ctx context.Context, flag string, defaultValue interface{}, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx["orgID"].(valuer.UUID))
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := license.FeatureVariants()[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[interface{}](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) Hooks() []openfeature.Hook {
|
||||
return []openfeature.Hook{}
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, evalCtx featuretypes.EvaluationContext) ([]*featuretypes.GettableFeature, error) {
|
||||
license, err := provider.licensing.GetActiveLicense(ctx, evalCtx.OrgID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return featuretypes.NewGettableFeatures(provider.registry.List(), license.FeatureVariants()), nil
|
||||
}
|
||||
1
ee/flagger/zeusprovider/provider.go
Normal file
1
ee/flagger/zeusprovider/provider.go
Normal file
@@ -0,0 +1 @@
|
||||
package zeusprovider
|
||||
1
go.mod
1
go.mod
@@ -35,6 +35,7 @@ require (
|
||||
github.com/knadh/koanf/v2 v2.1.1
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/open-feature/go-sdk v1.14.1
|
||||
github.com/open-telemetry/opamp-go v0.5.0
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.111.0
|
||||
github.com/opentracing/opentracing-go v1.2.0
|
||||
|
||||
3
go.sum
3
go.sum
@@ -348,6 +348,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -717,6 +718,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/open-feature/go-sdk v1.14.1 h1:jcxjCIG5Up3XkgYwWN5Y/WWfc6XobOhqrIwjyDBsoQo=
|
||||
github.com/open-feature/go-sdk v1.14.1/go.mod h1:t337k0VB/t/YxJ9S0prT30ISUHwYmUd/jhUZgFcOvGg=
|
||||
github.com/open-telemetry/opamp-go v0.5.0 h1:2YFbb6G4qBkq3yTRdVb5Nfz9hKHW/ldUyex352e1J7g=
|
||||
github.com/open-telemetry/opamp-go v0.5.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M=
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.111.0 h1:n1p2DedLvPEN1XEx26s1PR1PCuXTgCY4Eo+kDTq7q0s=
|
||||
|
||||
31
pkg/flagger/config.go
Normal file
31
pkg/flagger/config.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package flagger
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/factory"
|
||||
|
||||
var _ factory.Config = Config{}
|
||||
|
||||
type Config struct {
|
||||
Provider string `json:"provider"`
|
||||
Boolean Boolean `json:"boolean"`
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Enabled []string `json:"enabled"`
|
||||
Disabled []string `json:"disabled"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("flagger"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
return &Config{
|
||||
Provider: "memory",
|
||||
Boolean: Boolean{},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
227
pkg/flagger/flagger.go
Normal file
227
pkg/flagger/flagger.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package flagger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type Provider interface {
|
||||
openfeature.FeatureProvider
|
||||
|
||||
List(ctx context.Context, evalCtx featuretypes.EvaluationContext) ([]*featuretypes.GettableFeature, error)
|
||||
}
|
||||
|
||||
type Flagger interface {
|
||||
BooleanValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (bool, error)
|
||||
|
||||
StringValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (string, error)
|
||||
|
||||
FloatValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (float64, error)
|
||||
|
||||
IntValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (int64, error)
|
||||
|
||||
ObjectValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (interface{}, error)
|
||||
|
||||
List(ctx context.Context, evalCtx featuretypes.EvaluationContext) ([]*featuretypes.GettableFeature, error)
|
||||
}
|
||||
|
||||
type flagger struct {
|
||||
registry featuretypes.Registry
|
||||
settings factory.ScopedProviderSettings
|
||||
providers map[string]Provider
|
||||
clients map[string]*openfeature.Client
|
||||
}
|
||||
|
||||
func New(ctx context.Context, registry featuretypes.Registry, cfg Config, providerSettings factory.ProviderSettings, factories ...factory.ProviderFactory[Provider, Config]) (Flagger, error) {
|
||||
providers := make(map[string]Provider)
|
||||
clients := make(map[string]*openfeature.Client)
|
||||
for _, factory := range factories {
|
||||
provider, err := factory.New(ctx, providerSettings, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
providers[provider.Metadata().Name] = provider
|
||||
clients[provider.Metadata().Name] = openfeature.NewClient(provider.Metadata().Name)
|
||||
if err := openfeature.SetNamedProviderAndWait(provider.Metadata().Name, provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &flagger{
|
||||
registry: registry,
|
||||
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/flagger"),
|
||||
providers: providers,
|
||||
clients: clients,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) BooleanValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (bool, error) {
|
||||
feature, _, err := flagger.registry.Get(flag)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature from registry, defaulting to false", "error", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
defaultValue, _, err := featuretypes.GetFeatureVariantValue[bool](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get default variant value from registry, defaulting to false", "error", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, client := range flagger.clients {
|
||||
featureValue, err := client.BooleanValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureValue != defaultValue {
|
||||
return featureValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) StringValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (string, error) {
|
||||
feature, _, err := flagger.registry.Get(flag)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature from registry, defaulting to empty string", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
defaultValue, _, err := featuretypes.GetFeatureVariantValue[string](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get default variant value from registry, defaulting to empty string", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, client := range flagger.clients {
|
||||
featureValue, err := client.StringValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureValue != defaultValue {
|
||||
return featureValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) FloatValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (float64, error) {
|
||||
feature, _, err := flagger.registry.Get(flag)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature from registry, defaulting to 0", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defaultValue, _, err := featuretypes.GetFeatureVariantValue[float64](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get default variant value from registry, defaulting to 0", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, client := range flagger.clients {
|
||||
featureValue, err := client.FloatValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureValue != defaultValue {
|
||||
return featureValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) IntValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (int64, error) {
|
||||
feature, _, err := flagger.registry.Get(flag)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature from registry, defaulting to 0", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defaultValue, _, err := featuretypes.GetFeatureVariantValue[int64](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get default variant value from registry, defaulting to 0", "error", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, client := range flagger.clients {
|
||||
featureValue, err := client.IntValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureValue != defaultValue {
|
||||
return featureValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) ObjectValue(ctx context.Context, flag featuretypes.Name, evalCtx featuretypes.EvaluationContext) (interface{}, error) {
|
||||
feature, _, err := flagger.registry.Get(flag)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature from registry, defaulting to empty slice", "error", err)
|
||||
return []any{}, err
|
||||
}
|
||||
|
||||
defaultValue, _, err := featuretypes.GetFeatureVariantValue[interface{}](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
// This should never happen
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get default variant value from registry, defaulting to empty slice", "error", err)
|
||||
return []any{}, err
|
||||
}
|
||||
|
||||
for _, client := range flagger.clients {
|
||||
featureValue, err := client.ObjectValue(ctx, flag.String(), defaultValue, evalCtx.Ctx())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if featureValue != defaultValue {
|
||||
return featureValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func (flagger *flagger) List(ctx context.Context, evalCtx featuretypes.EvaluationContext) ([]*featuretypes.GettableFeature, error) {
|
||||
features := make([]*featuretypes.GettableFeature, 0)
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
mtx := sync.Mutex{}
|
||||
|
||||
for _, provider := range flagger.providers {
|
||||
wg.Add(1)
|
||||
go func(provider Provider) {
|
||||
defer wg.Done()
|
||||
providerFeatures, err := provider.List(ctx, evalCtx)
|
||||
if err != nil {
|
||||
flagger.settings.Logger().ErrorContext(ctx, "failed to get feature list from provider", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
mtx.Lock()
|
||||
features = append(features, providerFeatures...)
|
||||
mtx.Unlock()
|
||||
}(provider)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return features, nil
|
||||
}
|
||||
245
pkg/flagger/memoryprovider/provider.go
Normal file
245
pkg/flagger/memoryprovider/provider.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package memoryprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
config flagger.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
featureVariants map[featuretypes.Name]*featuretypes.FeatureVariant
|
||||
registry featuretypes.Registry
|
||||
}
|
||||
|
||||
func NewFactory(registry featuretypes.Registry) factory.ProviderFactory[flagger.Provider, flagger.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("memory"), func(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config) (flagger.Provider, error) {
|
||||
return New(ctx, providerSettings, config, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config flagger.Config, registry featuretypes.Registry) (flagger.Provider, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/flagger/memoryprovider")
|
||||
|
||||
featureVariants := make(map[featuretypes.Name]*featuretypes.FeatureVariant)
|
||||
for _, flag := range config.Boolean.Enabled {
|
||||
name, err := featuretypes.NewName(flag)
|
||||
if err != nil {
|
||||
settings.Logger().Error("invalid flag name encountered, skipping", "flag", flag, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
featureVariants[name] = &featuretypes.FeatureVariant{
|
||||
Variant: featuretypes.KindBooleanVariantEnabled,
|
||||
Value: true,
|
||||
}
|
||||
}
|
||||
|
||||
for _, flag := range config.Boolean.Disabled {
|
||||
name, err := featuretypes.NewName(flag)
|
||||
if err != nil {
|
||||
settings.Logger().Error("invalid flag name encountered, skipping", "flag", flag, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := featureVariants[name]; ok {
|
||||
settings.Logger().Error("flag already exists and has been enabled", "flag", flag)
|
||||
continue
|
||||
}
|
||||
|
||||
featureVariants[name] = &featuretypes.FeatureVariant{
|
||||
Variant: featuretypes.KindBooleanVariantDisabled,
|
||||
Value: false,
|
||||
}
|
||||
}
|
||||
|
||||
return &provider{
|
||||
config: config,
|
||||
settings: settings,
|
||||
featureVariants: featureVariants,
|
||||
registry: registry,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Metadata() openfeature.Metadata {
|
||||
return openfeature.Metadata{
|
||||
Name: "memory",
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureVariants[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[bool](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) StringEvaluation(ctx context.Context, flag string, defaultValue string, evalCtx openfeature.FlattenedContext) openfeature.StringResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureVariants[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[string](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) FloatEvaluation(ctx context.Context, flag string, defaultValue float64, evalCtx openfeature.FlattenedContext) openfeature.FloatResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureVariants[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[float64](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) IntEvaluation(ctx context.Context, flag string, defaultValue int64, evalCtx openfeature.FlattenedContext) openfeature.IntResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureVariants[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[int64](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) ObjectEvaluation(ctx context.Context, flag string, defaultValue interface{}, evalCtx openfeature.FlattenedContext) openfeature.InterfaceResolutionDetail {
|
||||
feature, detail, err := provider.registry.GetByNameString(flag)
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureVariants[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetFeatureVariantValue[interface{}](feature, featureValue.Variant)
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: value,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) Hooks() []openfeature.Hook {
|
||||
return []openfeature.Hook{}
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, evalCtx featuretypes.EvaluationContext) ([]*featuretypes.GettableFeature, error) {
|
||||
return featuretypes.NewGettableFeatures(provider.registry.List(), provider.featureVariants), nil
|
||||
}
|
||||
34
pkg/flagger/registry.go
Normal file
34
pkg/flagger/registry.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package flagger
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
var (
|
||||
FeatureUseTracesNewSchema = featuretypes.MustNewName("use_traces_new_schema")
|
||||
FeatureUseLogsNewSchema = featuretypes.MustNewName("use_logs_new_schema")
|
||||
)
|
||||
|
||||
func MustNewRegistry() featuretypes.Registry {
|
||||
registry, err := featuretypes.NewRegistry(
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureUseTracesNewSchema,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Description: "Use new traces schema",
|
||||
Stage: featuretypes.StageStable,
|
||||
DefaultVariant: featuretypes.KindBooleanVariantDisabled,
|
||||
Variants: featuretypes.NewKindBooleanFeatureVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureUseLogsNewSchema,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Description: "Use new logs schema",
|
||||
Stage: featuretypes.StageStable,
|
||||
DefaultVariant: featuretypes.KindBooleanVariantDisabled,
|
||||
Variants: featuretypes.NewKindBooleanFeatureVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
15
pkg/licensing/ilcensing.go
Normal file
15
pkg/licensing/ilcensing.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package licensing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Licensing interface {
|
||||
factory.Service
|
||||
|
||||
GetActiveLicense(context.Context, valuer.UUID) (licensetypes.License, error)
|
||||
}
|
||||
32
pkg/types/featuretypes/context.go
Normal file
32
pkg/types/featuretypes/context.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package featuretypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type EvaluationContext struct {
|
||||
ctx openfeature.EvaluationContext
|
||||
}
|
||||
|
||||
func NewEvaluationContext(orgID valuer.UUID) EvaluationContext {
|
||||
return EvaluationContext{
|
||||
ctx: openfeature.NewTargetlessEvaluationContext(map[string]interface{}{
|
||||
"orgId": orgID,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx EvaluationContext) Ctx() openfeature.EvaluationContext {
|
||||
return ctx.ctx
|
||||
}
|
||||
|
||||
func (ctx EvaluationContext) OrgID() valuer.UUID {
|
||||
orgId, ok := ctx.ctx.Attribute("orgId").(valuer.UUID)
|
||||
if !ok {
|
||||
// This should never happen
|
||||
return valuer.UUID{}
|
||||
}
|
||||
|
||||
return orgId
|
||||
}
|
||||
35
pkg/types/featuretypes/enum.go
Normal file
35
pkg/types/featuretypes/enum.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package featuretypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
const (
|
||||
KindBooleanVariantEnabled string = "enabled"
|
||||
KindBooleanVariantDisabled string = "disabled"
|
||||
)
|
||||
|
||||
var (
|
||||
KindBoolean Kind = Kind{valuer.NewString("bool")}
|
||||
KindString Kind = Kind{valuer.NewString("string")}
|
||||
KindInt Kind = Kind{valuer.NewString("int")}
|
||||
KindFloat Kind = Kind{valuer.NewString("float")}
|
||||
KindObject Kind = Kind{valuer.NewString("object")}
|
||||
)
|
||||
|
||||
// Kind is the kind of the feature flag.
|
||||
type Kind struct{ valuer.String }
|
||||
|
||||
var (
|
||||
// Is the feature experimental?
|
||||
StageExperimental = Stage{valuer.NewString("experimental")}
|
||||
|
||||
// Does the feature work but is not ready for production?
|
||||
StagePreview = Stage{valuer.NewString("preview")}
|
||||
|
||||
// Is the feature stable and ready for production?
|
||||
StageStable = Stage{valuer.NewString("stable")}
|
||||
|
||||
// Is the feature deprecated and will be removed in the future?
|
||||
StageDeprecated = Stage{valuer.NewString("deprecated")}
|
||||
)
|
||||
|
||||
type Stage struct{ valuer.String }
|
||||
101
pkg/types/featuretypes/feature.go
Normal file
101
pkg/types/featuretypes/feature.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package featuretypes
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeFeatureNotFound = errors.MustNewCode("feature_not_found")
|
||||
ErrCodeFeatureKindMismatch = errors.MustNewCode("feature_kind_mismatch")
|
||||
ErrCodeFeatureVariantNotFound = errors.MustNewCode("feature_variant_not_found")
|
||||
)
|
||||
|
||||
type GettableFeature struct {
|
||||
*Feature
|
||||
*FeatureVariant
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
// Name is the name of the feature flag.
|
||||
Name Name `json:"name"`
|
||||
|
||||
// Kind is the kind of the feature flag.
|
||||
Kind Kind `json:"kind"`
|
||||
|
||||
// Description is the description of the feature flag.
|
||||
Description string `json:"description"`
|
||||
|
||||
// Stage is the stage of the feature flag.
|
||||
Stage Stage `json:"stage"`
|
||||
|
||||
// DefaultVariant is the default variant of the feature flag.
|
||||
DefaultVariant string `json:"defaultVariant"`
|
||||
|
||||
// Variants is the variants of the feature flag.
|
||||
Variants map[string]any `json:"variants"`
|
||||
}
|
||||
|
||||
type FeatureVariant struct {
|
||||
// Variant is the variant of the feature flag.
|
||||
Variant string `json:"variant"`
|
||||
|
||||
// Value is the value of the feature flag.
|
||||
Value any `json:"value"`
|
||||
}
|
||||
|
||||
func NewKindBooleanFeatureVariants() map[string]any {
|
||||
return map[string]any{
|
||||
KindBooleanVariantEnabled: true,
|
||||
KindBooleanVariantDisabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
func GetFeatureVariantValue[T any](feature *Feature, variant string) (t T, detail openfeature.ProviderResolutionDetail, err error) {
|
||||
value, ok := feature.Variants[variant]
|
||||
if !ok {
|
||||
err = errors.Newf(errors.TypeNotFound, ErrCodeFeatureVariantNotFound, "variant %s not found for feature %s", variant, feature.Name.String())
|
||||
detail = openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewGeneralResolutionError(err.Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
t, ok = value.(T)
|
||||
if !ok {
|
||||
err = errors.Newf(errors.TypeInvalidInput, ErrCodeFeatureKindMismatch, "variant %s has type %T, expected %T", variant, value, t)
|
||||
detail = openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewTypeMismatchResolutionError(err.Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
Variant: feature.DefaultVariant,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
detail = openfeature.ProviderResolutionDetail{
|
||||
Reason: openfeature.StaticReason,
|
||||
Variant: variant,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewGettableFeatures(features []*Feature, featureVariants map[Name]*FeatureVariant) []*GettableFeature {
|
||||
gettableFeatures := make([]*GettableFeature, 0)
|
||||
|
||||
for _, feature := range features {
|
||||
if featureVariant, ok := featureVariants[feature.Name]; ok {
|
||||
gettableFeatures = append(gettableFeatures, &GettableFeature{Feature: feature, FeatureVariant: featureVariant})
|
||||
continue
|
||||
}
|
||||
|
||||
gettableFeatures = append(gettableFeatures, &GettableFeature{Feature: feature, FeatureVariant: &FeatureVariant{
|
||||
Variant: feature.DefaultVariant,
|
||||
Value: feature.Variants[feature.DefaultVariant],
|
||||
}})
|
||||
}
|
||||
|
||||
return gettableFeatures
|
||||
}
|
||||
35
pkg/types/featuretypes/name.go
Normal file
35
pkg/types/featuretypes/name.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package featuretypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
nameRegex = regexp.MustCompile(`^[a-z][a-z0-9_]+$`)
|
||||
)
|
||||
|
||||
type Name struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func NewName(s string) (Name, error) {
|
||||
if !nameRegex.MatchString(s) {
|
||||
return Name{}, fmt.Errorf("invalid feature name: %s", s)
|
||||
}
|
||||
|
||||
return Name{s: s}, nil
|
||||
}
|
||||
|
||||
func MustNewName(s string) Name {
|
||||
name, err := NewName(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (n Name) String() string {
|
||||
return n.s
|
||||
}
|
||||
153
pkg/types/featuretypes/registry.go
Normal file
153
pkg/types/featuretypes/registry.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package featuretypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type Registry interface {
|
||||
// Merge merges the given registry with the current registry and returns a new registry.
|
||||
// The input registry takes precedence over the current registry.
|
||||
MergeOrOverride(Registry) Registry
|
||||
|
||||
// Get returns the feature with the given name.
|
||||
Get(Name) (*Feature, openfeature.ProviderResolutionDetail, error)
|
||||
|
||||
// GetByNameString returns the feature with the given name string.
|
||||
GetByNameString(string) (*Feature, openfeature.ProviderResolutionDetail, error)
|
||||
|
||||
// List returns all the features in the registry.
|
||||
List() []*Feature
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
features map[Name]*Feature
|
||||
}
|
||||
|
||||
func NewRegistry(features ...*Feature) (Registry, error) {
|
||||
registry := ®istry{features: make(map[Name]*Feature)}
|
||||
|
||||
for _, feature := range features {
|
||||
// Name must be unique
|
||||
if _, ok := registry.features[feature.Name]; ok {
|
||||
return nil, fmt.Errorf("cannot build registry, duplicate name %q found", feature.Name.String())
|
||||
}
|
||||
|
||||
// Default variant must be present in the variants
|
||||
if _, ok := feature.Variants[feature.DefaultVariant]; !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q not found in variants %v", feature.DefaultVariant, feature.Variants)
|
||||
}
|
||||
|
||||
// Check that the type of the default variant and the variants match the kind of the feature
|
||||
switch feature.Kind {
|
||||
case KindBoolean:
|
||||
_, _, err := GetFeatureVariantValue[bool](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for variant := range feature.Variants {
|
||||
_, _, err := GetFeatureVariantValue[bool](feature, variant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case KindString:
|
||||
_, _, err := GetFeatureVariantValue[string](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for variant := range feature.Variants {
|
||||
_, _, err := GetFeatureVariantValue[string](feature, variant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case KindInt:
|
||||
_, _, err := GetFeatureVariantValue[int](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for variant := range feature.Variants {
|
||||
_, _, err := GetFeatureVariantValue[int](feature, variant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case KindFloat:
|
||||
_, _, err := GetFeatureVariantValue[float64](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for variant := range feature.Variants {
|
||||
_, _, err := GetFeatureVariantValue[float64](feature, variant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case KindObject:
|
||||
_, _, err := GetFeatureVariantValue[map[string]any](feature, feature.DefaultVariant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for variant := range feature.Variants {
|
||||
_, _, err := GetFeatureVariantValue[map[string]any](feature, variant)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.features[feature.Name] = feature
|
||||
}
|
||||
|
||||
return registry, nil
|
||||
}
|
||||
|
||||
func (registry *registry) MergeOrOverride(other Registry) Registry {
|
||||
for _, feature := range other.List() {
|
||||
registry.features[feature.Name] = feature
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
func (registry *registry) Get(name Name) (*Feature, openfeature.ProviderResolutionDetail, error) {
|
||||
feature, ok := registry.features[name]
|
||||
if !ok {
|
||||
err := errors.Newf(errors.TypeNotFound, ErrCodeFeatureNotFound, "feature %s not found in registry", name.String())
|
||||
return nil, openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewFlagNotFoundResolutionError(err.Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
}, err
|
||||
}
|
||||
|
||||
return feature, openfeature.ProviderResolutionDetail{}, nil
|
||||
}
|
||||
|
||||
func (registry *registry) GetByNameString(nameString string) (*Feature, openfeature.ProviderResolutionDetail, error) {
|
||||
name, err := NewName(nameString)
|
||||
if err != nil {
|
||||
return nil, openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewFlagNotFoundResolutionError(err.Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
}, err
|
||||
}
|
||||
|
||||
return registry.Get(name)
|
||||
}
|
||||
|
||||
func (registry *registry) List() []*Feature {
|
||||
features := make([]*Feature, 0, len(registry.features))
|
||||
for _, feature := range registry.features {
|
||||
features = append(features, feature)
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
31
pkg/types/licensetypes/license.go
Normal file
31
pkg/types/licensetypes/license.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package licensetypes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type License interface {
|
||||
// ID returns the unique identifier for the license
|
||||
ID() valuer.UUID
|
||||
|
||||
// OrgID returns the organization ID for the license
|
||||
OrgID() valuer.UUID
|
||||
|
||||
// Contents returns the raw data for the license
|
||||
Contents() []byte
|
||||
|
||||
// Key returns the key for the license
|
||||
Key() string
|
||||
|
||||
// CreatedAt returns the creation time for the license
|
||||
CreatedAt() time.Time
|
||||
|
||||
// UpdatedAt returns the last update time for the license
|
||||
UpdatedAt() time.Time
|
||||
|
||||
// FeatureValues returns the feature values for the license
|
||||
FeatureVariants() map[featuretypes.Name]*featuretypes.FeatureVariant
|
||||
}
|
||||
Reference in New Issue
Block a user