Compare commits
4 Commits
feat/issue
...
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/knadh/koanf/v2 v2.1.1
|
||||||
github.com/mailru/easyjson v0.7.7
|
github.com/mailru/easyjson v0.7.7
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
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/opamp-go v0.5.0
|
||||||
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.111.0
|
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.111.0
|
||||||
github.com/opentracing/opentracing-go v1.2.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.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.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
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/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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/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.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
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 h1:2YFbb6G4qBkq3yTRdVb5Nfz9hKHW/ldUyex352e1J7g=
|
||||||
github.com/open-telemetry/opamp-go v0.5.0/go.mod h1:IMdeuHGVc5CjKSu5/oNV0o+UmiXuahoHvoZ4GOmAI9M=
|
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=
|
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