Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4af0503a89 | ||
|
|
fa2f63bc0d | ||
|
|
084def0ba9 | ||
|
|
b77bca7b71 | ||
|
|
a6bd5f9e33 | ||
|
|
2ae34cbcae | ||
|
|
46e8182ab1 | ||
|
|
7bc2a614f9 | ||
|
|
88227c6992 | ||
|
|
6aa8f18018 | ||
|
|
a389901b8d | ||
|
|
30e2581bbc | ||
|
|
ff1e46766f | ||
|
|
8e48e58f9b | ||
|
|
bb7301bc9f | ||
|
|
7b5eae84d5 | ||
|
|
0aeb1009c6 | ||
|
|
ab3b250629 | ||
|
|
765354b1ee | ||
|
|
a3daf43186 | ||
|
|
1649c0e26f | ||
|
|
142ad8adc4 | ||
|
|
cda94d0325 | ||
|
|
51ae2df8d5 | ||
|
|
ea4e9988a5 | ||
|
|
74489efeef | ||
|
|
834d75fbbd |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -83,3 +83,4 @@ queries.active
|
||||
|
||||
# .devenv tmp files
|
||||
.devenv/**/tmp/**
|
||||
.qodo
|
||||
|
||||
31
ee/licensing/licensingserver/config.go
Normal file
31
ee/licensing/licensingserver/config.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package licensingserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
PollingConfig PollingConfig `mapstructure:"polling"`
|
||||
}
|
||||
|
||||
type PollingConfig struct {
|
||||
Interval time.Duration `mapstructure:"interval"`
|
||||
}
|
||||
|
||||
func NewConfig() Config {
|
||||
return Config{
|
||||
PollingConfig: PollingConfig{
|
||||
Interval: 24 * time.Hour,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewConfigFromLicensingConfig(config licensing.Config) Config {
|
||||
return Config{
|
||||
PollingConfig: PollingConfig{
|
||||
Interval: config.PollingConfig.Interval,
|
||||
},
|
||||
}
|
||||
}
|
||||
72
ee/licensing/licensingserver/server.go
Normal file
72
ee/licensing/licensingserver/server.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package licensingserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
logger *slog.Logger
|
||||
|
||||
cfg Config
|
||||
|
||||
orgID valuer.UUID
|
||||
|
||||
zeus zeus.Zeus
|
||||
|
||||
store licensetypes.Store
|
||||
|
||||
license licensetypes.License
|
||||
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func NewServer(logger *slog.Logger, config Config, orgID valuer.UUID, zeus zeus.Zeus, store licensetypes.Store) *Server {
|
||||
return &Server{
|
||||
logger: logger,
|
||||
cfg: config,
|
||||
orgID: orgID,
|
||||
zeus: zeus,
|
||||
store: store,
|
||||
license: licensetypes.NewNoop(),
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) Fetch(ctx context.Context) error {
|
||||
license, err := server.store.GetLatest(ctx, server.orgID)
|
||||
if err != nil {
|
||||
if errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
fetchedLicense, err := server.zeus.GetLicense(ctx, license.Key())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return server.SetLicense(ctx, fetchedLicense)
|
||||
}
|
||||
|
||||
func (server *Server) SetLicense(ctx context.Context, license licensetypes.License) error {
|
||||
server.mtx.Lock()
|
||||
defer server.mtx.Unlock()
|
||||
|
||||
server.license = license
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) GetLicense(ctx context.Context) licensetypes.License {
|
||||
server.mtx.RLock()
|
||||
defer server.mtx.RUnlock()
|
||||
|
||||
return server.license
|
||||
}
|
||||
35
ee/licensing/licensingstore/sqllicensingstore/store.go
Normal file
35
ee/licensing/licensingstore/sqllicensingstore/store.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package sqllicensingstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) licensetypes.Store {
|
||||
return &store{
|
||||
sqlstore: sqlstore,
|
||||
}
|
||||
}
|
||||
|
||||
func (store *store) Set(ctx context.Context, license licensetypes.License) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID) ([]licensetypes.License, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (store *store) GetLatest(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (store *store) ListOrgs(ctx context.Context) ([]valuer.UUID, error) {
|
||||
return nil, nil
|
||||
}
|
||||
109
ee/licensing/pollinglicensing/provider.go
Normal file
109
ee/licensing/pollinglicensing/provider.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package pollinglicensing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
config licensing.Config
|
||||
settings factory.ScopedProviderSettings
|
||||
zeus zeus.Zeus
|
||||
service *Service
|
||||
store licensetypes.Store
|
||||
stopC chan struct{}
|
||||
}
|
||||
|
||||
func NewFactory(zeus zeus.Zeus, sqlstore sqlstore.SQLStore) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("sql"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||
return New(ctx, providerSettings, config, zeus, sqlstore)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config, zeus zeus.Zeus, sqlstore sqlstore.SQLStore) (licensing.Licensing, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/licensing/pollinglicensing")
|
||||
store := sqllicensingstore.NewStore(sqlstore)
|
||||
|
||||
return &provider{
|
||||
config: config,
|
||||
settings: settings,
|
||||
zeus: zeus,
|
||||
service: NewService(ctx, settings, config, store, zeus),
|
||||
stopC: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
if err := provider.service.SyncServers(ctx); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to sync licensing servers", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(provider.config.PollingConfig.Interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-provider.stopC:
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
if err := provider.service.SyncServers(ctx); err != nil {
|
||||
provider.settings.Logger().ErrorContext(ctx, "failed to sync licensing servers", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *provider) GetLatestLicense(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
|
||||
server, err := provider.service.getServer(orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return server.GetLicense(ctx), nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetLicenses(ctx context.Context, orgID valuer.UUID, params licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error) {
|
||||
if params.Active != nil {
|
||||
if *params.Active {
|
||||
license, err := provider.GetLatestLicense(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licensetypes.GettableLicenses{license}, nil
|
||||
}
|
||||
}
|
||||
|
||||
licenses, err := provider.store.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
func (provider *provider) SetLicense(ctx context.Context, orgID valuer.UUID, key string) error {
|
||||
license, err := provider.zeus.GetLicense(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := provider.store.Set(ctx, license); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return provider.service.SyncOrgServer(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
close(provider.stopC)
|
||||
return nil
|
||||
}
|
||||
103
ee/licensing/pollinglicensing/service.go
Normal file
103
ee/licensing/pollinglicensing/service.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package pollinglicensing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/licensingserver"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
// config is the config for the licensing service
|
||||
config licensing.Config
|
||||
|
||||
// store is the store for the licensing service
|
||||
store licensetypes.Store
|
||||
|
||||
// zeus
|
||||
zeus zeus.Zeus
|
||||
|
||||
// settings is the settings for the licensing service
|
||||
settings factory.ScopedProviderSettings
|
||||
|
||||
// Map of organization id to alertmanager server
|
||||
servers map[valuer.UUID]*licensingserver.Server
|
||||
|
||||
// Mutex to protect the servers map
|
||||
serversMtx sync.RWMutex
|
||||
}
|
||||
|
||||
func NewService(ctx context.Context, settings factory.ScopedProviderSettings, config licensing.Config, store licensetypes.Store, zeus zeus.Zeus) *Service {
|
||||
service := &Service{
|
||||
config: config,
|
||||
store: store,
|
||||
zeus: zeus,
|
||||
settings: settings,
|
||||
servers: make(map[valuer.UUID]*licensingserver.Server),
|
||||
serversMtx: sync.RWMutex{},
|
||||
}
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
func (service *Service) SyncServers(ctx context.Context) error {
|
||||
orgIDs, err := service.store.ListOrgs(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
service.serversMtx.Lock()
|
||||
for _, orgID := range orgIDs {
|
||||
// If the server is not present, create it and sync the config
|
||||
if _, ok := service.servers[orgID]; !ok {
|
||||
server := licensingserver.NewServer(service.settings.Logger(), licensingserver.NewConfigFromLicensingConfig(service.config), orgID, service.zeus, service.store)
|
||||
service.servers[orgID] = server
|
||||
}
|
||||
|
||||
err = service.servers[orgID].Fetch(ctx)
|
||||
if err != nil {
|
||||
service.settings.Logger().Error("failed to fetch license for licensing server", "orgID", orgID, "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
service.serversMtx.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) SyncOrgServer(ctx context.Context, orgID valuer.UUID) error {
|
||||
service.serversMtx.Lock()
|
||||
defer service.serversMtx.Unlock()
|
||||
|
||||
_, ok := service.servers[orgID]
|
||||
if !ok {
|
||||
server := licensingserver.NewServer(service.settings.Logger(), licensingserver.NewConfigFromLicensingConfig(service.config), orgID, service.zeus, service.store)
|
||||
service.servers[orgID] = server
|
||||
}
|
||||
|
||||
err := service.servers[orgID].Fetch(ctx)
|
||||
if err != nil {
|
||||
service.settings.Logger().Error("failed to fetch license for licensing server", "orgID", orgID, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service *Service) getServer(orgID valuer.UUID) (*licensingserver.Server, error) {
|
||||
service.serversMtx.RLock()
|
||||
defer service.serversMtx.RUnlock()
|
||||
|
||||
server, ok := service.servers[orgID]
|
||||
if !ok {
|
||||
return nil, errors.Newf(errors.TypeNotFound, licensing.ErrCodeLicensingServerNotFound, "server not found for %s", orgID.StringValue())
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/ee/query-service/license"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
@@ -29,7 +28,7 @@ import (
|
||||
)
|
||||
|
||||
type APIHandlerOptions struct {
|
||||
DataConnector interfaces.DataConnector
|
||||
DataConnector baseint.Reader
|
||||
SkipConfig *basemodel.SkipConfig
|
||||
PreferSpanMetrics bool
|
||||
AppDao dao.ModelDao
|
||||
|
||||
@@ -222,7 +222,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// start the usagemanager
|
||||
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.Config.TelemetryStore.Clickhouse.DSN)
|
||||
usageManager, err := usage.New(lm.GetRepo(), serverOptions.SigNoz.TelemetryStore, serverOptions.SigNoz.Zeus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
)
|
||||
|
||||
// Connector defines methods for interaction
|
||||
// with o11y data. for example - clickhouse
|
||||
type DataConnector interface {
|
||||
baseint.Reader
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
|
||||
validate "github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
@@ -29,6 +29,7 @@ var validationFrequency = 24 * 60 * time.Minute
|
||||
|
||||
type Manager struct {
|
||||
repo *Repo
|
||||
zeus zeus.Zeus
|
||||
mutex sync.Mutex
|
||||
validatorRunning bool
|
||||
// end the license validation, this is important to gracefully
|
||||
@@ -45,7 +46,7 @@ type Manager struct {
|
||||
activeFeatures basemodel.FeatureSet
|
||||
}
|
||||
|
||||
func StartManager(db *sqlx.DB, store sqlstore.SQLStore, features ...basemodel.Feature) (*Manager, error) {
|
||||
func StartManager(db *sqlx.DB, store sqlstore.SQLStore, zeus zeus.Zeus, features ...basemodel.Feature) (*Manager, error) {
|
||||
if LM != nil {
|
||||
return LM, nil
|
||||
}
|
||||
@@ -53,6 +54,7 @@ func StartManager(db *sqlx.DB, store sqlstore.SQLStore, features ...basemodel.Fe
|
||||
repo := NewLicenseRepo(db, store)
|
||||
m := &Manager{
|
||||
repo: &repo,
|
||||
zeus: zeus,
|
||||
}
|
||||
if err := m.start(features...); err != nil {
|
||||
return m, err
|
||||
@@ -173,14 +175,12 @@ func (lm *Manager) ValidatorV3(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
|
||||
|
||||
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
|
||||
if apiError != nil {
|
||||
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
|
||||
return apiError
|
||||
license, err := lm.zeus.GetLicense(ctx, lm.activeLicenseV3.Key)
|
||||
if err != nil {
|
||||
return model.BadRequest(errors.Wrap(err, "failed to get license"))
|
||||
}
|
||||
|
||||
err := lm.repo.UpdateLicenseV3(ctx, license)
|
||||
err = lm.repo.UpdateLicenseV3(ctx, license)
|
||||
if err != nil {
|
||||
return model.BadRequest(errors.Wrap(err, "failed to update the new license"))
|
||||
}
|
||||
@@ -247,10 +247,9 @@ func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseRe
|
||||
}
|
||||
}()
|
||||
|
||||
license, apiError := validate.ValidateLicenseV3(licenseKey)
|
||||
if apiError != nil {
|
||||
zap.L().Error("failed to get the license", zap.Error(apiError.Err))
|
||||
return nil, apiError
|
||||
license, errv2 := lm.zeus.GetLicense(ctx, lm.activeLicenseV3.Key)
|
||||
if errv2 != nil {
|
||||
return nil, model.BadRequest(errors.Wrap(errv2, "failed to get license"))
|
||||
}
|
||||
|
||||
// insert the new license to the sqlite db
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
Key string `json:"key" db:"key"`
|
||||
ActivationId string `json:"activationId" db:"activationId"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
|
||||
// PlanDetails contains the encrypted plan info
|
||||
PlanDetails string `json:"planDetails" db:"planDetails"`
|
||||
|
||||
// stores parsed license details
|
||||
LicensePlan
|
||||
|
||||
FeatureSet basemodel.FeatureSet
|
||||
|
||||
// populated in case license has any errors
|
||||
ValidationMessage string `db:"validationMessage"`
|
||||
|
||||
// used only for sending details to front-end
|
||||
IsCurrent bool `json:"isCurrent"`
|
||||
}
|
||||
|
||||
func (l *License) MarshalJSON() ([]byte, error) {
|
||||
|
||||
return json.Marshal(&struct {
|
||||
Key string `json:"key" db:"key"`
|
||||
ActivationId string `json:"activationId" db:"activationId"`
|
||||
ValidationMessage string `db:"validationMessage"`
|
||||
IsCurrent bool `json:"isCurrent"`
|
||||
PlanKey string `json:"planKey"`
|
||||
ValidFrom time.Time `json:"ValidFrom"`
|
||||
ValidUntil time.Time `json:"ValidUntil"`
|
||||
Status string `json:"status"`
|
||||
}{
|
||||
Key: l.Key,
|
||||
ActivationId: l.ActivationId,
|
||||
IsCurrent: l.IsCurrent,
|
||||
PlanKey: l.PlanKey,
|
||||
ValidFrom: time.Unix(l.ValidFrom, 0),
|
||||
ValidUntil: time.Unix(l.ValidUntil, 0),
|
||||
Status: l.Status,
|
||||
ValidationMessage: l.ValidationMessage,
|
||||
})
|
||||
}
|
||||
|
||||
type LicensePlan struct {
|
||||
PlanKey string `json:"planKey"`
|
||||
ValidFrom int64 `json:"validFrom"`
|
||||
ValidUntil int64 `json:"validUntil"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type Licenses struct {
|
||||
TrialStart int64 `json:"trialStart"`
|
||||
TrialEnd int64 `json:"trialEnd"`
|
||||
OnTrial bool `json:"onTrial"`
|
||||
WorkSpaceBlock bool `json:"workSpaceBlock"`
|
||||
TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"`
|
||||
GracePeriodEnd int64 `json:"gracePeriodEnd"`
|
||||
Licenses []License `json:"licenses"`
|
||||
}
|
||||
|
||||
type SubscriptionServerResp struct {
|
||||
Status string `json:"status"`
|
||||
Data Licenses `json:"data"`
|
||||
}
|
||||
|
||||
type Plan struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LicenseDB struct {
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
type LicenseV3 struct {
|
||||
ID string
|
||||
Key string
|
||||
Data map[string]interface{}
|
||||
PlanName string
|
||||
Features basemodel.FeatureSet
|
||||
Status string
|
||||
IsCurrent bool
|
||||
ValidFrom int64
|
||||
ValidUntil int64
|
||||
}
|
||||
|
||||
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
|
||||
var zeroValue T
|
||||
if val, ok := data[key]; ok {
|
||||
if value, ok := val.(T); ok {
|
||||
return value, nil
|
||||
}
|
||||
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
|
||||
}
|
||||
return zeroValue, fmt.Errorf("%s key is missing", key)
|
||||
}
|
||||
|
||||
func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
||||
var features basemodel.FeatureSet
|
||||
|
||||
// extract id from data
|
||||
licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(data, "id")
|
||||
|
||||
// extract key from data
|
||||
licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delete(data, "key")
|
||||
|
||||
// extract status from data
|
||||
status, err := extractKeyFromMapStringInterface[string](data, "status")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if license status is invalid then default it to basic
|
||||
if status == LicenseStatusInvalid {
|
||||
planName = PlanNameBasic
|
||||
}
|
||||
|
||||
featuresFromZeus := basemodel.FeatureSet{}
|
||||
if _features, ok := data["features"]; ok {
|
||||
featuresData, err := json.Marshal(_features)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to marshal features data")
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal features data")
|
||||
}
|
||||
}
|
||||
|
||||
switch planName {
|
||||
case PlanNameTeams:
|
||||
features = append(features, ProPlan...)
|
||||
case PlanNameEnterprise:
|
||||
features = append(features, EnterprisePlan...)
|
||||
case PlanNameBasic:
|
||||
features = append(features, BasicPlan...)
|
||||
default:
|
||||
features = append(features, BasicPlan...)
|
||||
}
|
||||
|
||||
if len(featuresFromZeus) > 0 {
|
||||
for _, feature := range featuresFromZeus {
|
||||
exists := false
|
||||
for i, existingFeature := range features {
|
||||
if existingFeature.Name == feature.Name {
|
||||
features[i] = feature // Replace existing feature
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
features = append(features, feature) // Append if it doesn't exist
|
||||
}
|
||||
}
|
||||
}
|
||||
data["features"] = features
|
||||
|
||||
_validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
|
||||
if err != nil {
|
||||
_validFrom = 0
|
||||
}
|
||||
validFrom := int64(_validFrom)
|
||||
|
||||
_validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
|
||||
if err != nil {
|
||||
_validUntil = 0
|
||||
}
|
||||
validUntil := int64(_validUntil)
|
||||
|
||||
return &LicenseV3{
|
||||
ID: licenseID,
|
||||
Key: licenseKey,
|
||||
Data: data,
|
||||
PlanName: planName,
|
||||
Features: features,
|
||||
ValidFrom: validFrom,
|
||||
ValidUntil: validUntil,
|
||||
Status: status,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
|
||||
licenseDataWithIdAndKey := data
|
||||
licenseDataWithIdAndKey["id"] = id
|
||||
licenseDataWithIdAndKey["key"] = key
|
||||
return NewLicenseV3(licenseDataWithIdAndKey)
|
||||
}
|
||||
|
||||
func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
|
||||
planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
|
||||
if !ok {
|
||||
planKeyFromPlanName = Basic
|
||||
}
|
||||
return &License{
|
||||
Key: l.Key,
|
||||
ActivationId: "",
|
||||
PlanDetails: "",
|
||||
FeatureSet: l.Features,
|
||||
ValidationMessage: "",
|
||||
IsCurrent: l.IsCurrent,
|
||||
LicensePlan: LicensePlan{
|
||||
PlanKey: planKeyFromPlanName,
|
||||
ValidFrom: l.ValidFrom,
|
||||
ValidUntil: l.ValidUntil,
|
||||
Status: l.Status},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type CheckoutRequest struct {
|
||||
SuccessURL string `json:"url"`
|
||||
}
|
||||
|
||||
type PortalRequest struct {
|
||||
SuccessURL string `json:"url"`
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewLicenseV3(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
data []byte
|
||||
pass bool
|
||||
expected *LicenseV3
|
||||
error error
|
||||
}{
|
||||
{
|
||||
name: "Error for missing license id",
|
||||
data: []byte(`{}`),
|
||||
pass: false,
|
||||
error: errors.New("id key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for license id not being a valid string",
|
||||
data: []byte(`{"id": 10}`),
|
||||
pass: false,
|
||||
error: errors.New("id key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license key",
|
||||
data: []byte(`{"id":"does-not-matter"}`),
|
||||
pass: false,
|
||||
error: errors.New("key key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid string license key",
|
||||
data: []byte(`{"id":"does-not-matter","key":10}`),
|
||||
pass: false,
|
||||
error: errors.New("key key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license status",
|
||||
data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
|
||||
pass: false,
|
||||
error: errors.New("status key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid string license status",
|
||||
data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
|
||||
pass: false,
|
||||
error: errors.New("status key is not a valid string"),
|
||||
},
|
||||
{
|
||||
name: "Error for missing license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
|
||||
pass: false,
|
||||
error: errors.New("plan key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid json license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
|
||||
pass: false,
|
||||
error: errors.New("plan key is not a valid map[string]interface {}"),
|
||||
},
|
||||
{
|
||||
name: "Error for invalid license plan",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
|
||||
pass: false,
|
||||
error: errors.New("name key is missing"),
|
||||
},
|
||||
{
|
||||
name: "Parse the entire license properly",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
"valid_from": float64(1730899309),
|
||||
"valid_until": float64(-1),
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "ACTIVE",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Fallback to basic plan if license status is invalid",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"category": "FREE",
|
||||
"status": "INVALID",
|
||||
"valid_from": float64(1730899309),
|
||||
"valid_until": float64(-1),
|
||||
},
|
||||
PlanName: PlanNameBasic,
|
||||
ValidFrom: 1730899309,
|
||||
ValidUntil: -1,
|
||||
Status: "INVALID",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fallback states for validFrom and validUntil",
|
||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||
pass: true,
|
||||
expected: &LicenseV3{
|
||||
ID: "does-not-matter",
|
||||
Key: "does-not-matter-key",
|
||||
Data: map[string]interface{}{
|
||||
"plan": map[string]interface{}{
|
||||
"name": "TEAMS",
|
||||
},
|
||||
"valid_from": 1234.456,
|
||||
"valid_until": 5678.567,
|
||||
"category": "FREE",
|
||||
"status": "ACTIVE",
|
||||
},
|
||||
PlanName: PlanNameTeams,
|
||||
ValidFrom: 1234,
|
||||
ValidUntil: 5678,
|
||||
Status: "ACTIVE",
|
||||
IsCurrent: false,
|
||||
Features: model.FeatureSet{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
var licensePayload map[string]interface{}
|
||||
err := json.Unmarshal(tc.data, &licensePayload)
|
||||
require.NoError(t, err)
|
||||
license, err := NewLicenseV3(licensePayload)
|
||||
if license != nil {
|
||||
license.Features = make(model.FeatureSet, 0)
|
||||
delete(license.Data, "features")
|
||||
}
|
||||
|
||||
if tc.pass {
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, license)
|
||||
assert.Equal(t, tc.expected, license)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, tc.error.Error())
|
||||
require.Nil(t, license)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
@@ -24,7 +23,6 @@ var (
|
||||
LicenseStatusInvalid = "INVALID"
|
||||
)
|
||||
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
const Gateway = "GATEWAY"
|
||||
@@ -38,83 +36,6 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: DisableUpsell,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@@ -143,135 +64,6 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
var ProPlan = basemodel.FeatureSet{
|
||||
basemodel.Feature{
|
||||
Name: SSO,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: Gateway,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: PremiumSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AnomalyDetection,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
@@ -289,76 +81,6 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.OSS,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.CustomMetricsFunction,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.UseSpanMetrics,
|
||||
Active: false,
|
||||
@@ -401,13 +123,6 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.HostsInfraMonitoring,
|
||||
Active: constants.EnableHostsInfraMonitoring(),
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.TraceFunnels,
|
||||
Active: false,
|
||||
|
||||
@@ -4,22 +4,21 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/go-co-op/gocron"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
licenseserver "github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
|
||||
"github.com/SigNoz/signoz/ee/query-service/license"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/encryption"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,35 +33,20 @@ var (
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
clickhouseConn clickhouse.Conn
|
||||
|
||||
licenseRepo *license.Repo
|
||||
|
||||
scheduler *gocron.Scheduler
|
||||
|
||||
modelDao dao.ModelDao
|
||||
|
||||
tenantID string
|
||||
telemetryStore telemetrystore.TelemetryStore
|
||||
licenseRepo *license.Repo
|
||||
scheduler *gocron.Scheduler
|
||||
zeus zeus.Zeus
|
||||
}
|
||||
|
||||
func New(modelDao dao.ModelDao, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn, chUrl string) (*Manager, error) {
|
||||
hostNameRegex := regexp.MustCompile(`tcp://(?P<hostname>.*):`)
|
||||
hostNameRegexMatches := hostNameRegex.FindStringSubmatch(chUrl)
|
||||
|
||||
tenantID := ""
|
||||
if len(hostNameRegexMatches) == 2 {
|
||||
tenantID = hostNameRegexMatches[1]
|
||||
tenantID = strings.TrimSuffix(tenantID, "-clickhouse")
|
||||
}
|
||||
|
||||
func New(licenseRepo *license.Repo, telemetryStore telemetrystore.TelemetryStore, zeus zeus.Zeus) (*Manager, error) {
|
||||
m := &Manager{
|
||||
// repository: repo,
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseRepo: licenseRepo,
|
||||
telemetryStore: telemetryStore,
|
||||
zeus: zeus,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
modelDao: modelDao,
|
||||
tenantID: tenantID,
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@@ -120,7 +104,7 @@ func (lm *Manager) UploadUsage() {
|
||||
|
||||
for _, db := range dbs {
|
||||
dbusages := []model.UsageDB{}
|
||||
err := lm.clickhouseConn.Select(ctx, &dbusages, fmt.Sprintf(query, db, db), time.Now().Add(-(24 * time.Hour)))
|
||||
err := lm.telemetryStore.ClickhouseDB().Select(ctx, &dbusages, fmt.Sprintf(query, db, db), time.Now().Add(-(24 * time.Hour)))
|
||||
if err != nil && !strings.Contains(err.Error(), "doesn't exist") {
|
||||
zap.L().Error("failed to get usage from clickhouse: %v", zap.Error(err))
|
||||
return
|
||||
@@ -136,17 +120,6 @@ func (lm *Manager) UploadUsage() {
|
||||
return
|
||||
}
|
||||
|
||||
zap.L().Info("uploading usage data")
|
||||
|
||||
orgName := ""
|
||||
orgNames, orgError := lm.modelDao.GetOrgs(ctx)
|
||||
if orgError != nil {
|
||||
zap.L().Error("failed to get org data: %v", zap.Error(orgError))
|
||||
}
|
||||
if len(orgNames) == 1 {
|
||||
orgName = orgNames[0].Name
|
||||
}
|
||||
|
||||
usagesPayload := []model.Usage{}
|
||||
for _, usage := range usages {
|
||||
usageDataBytes, err := encryption.Decrypt([]byte(usage.ExporterID[:32]), []byte(usage.Data))
|
||||
@@ -166,8 +139,8 @@ func (lm *Manager) UploadUsage() {
|
||||
usageData.ExporterID = usage.ExporterID
|
||||
usageData.Type = usage.Type
|
||||
usageData.Tenant = "default"
|
||||
usageData.OrgName = orgName
|
||||
usageData.TenantId = lm.tenantID
|
||||
usageData.OrgName = "default"
|
||||
usageData.TenantId = "default"
|
||||
usagesPayload = append(usagesPayload, usageData)
|
||||
}
|
||||
|
||||
@@ -176,6 +149,7 @@ func (lm *Manager) UploadUsage() {
|
||||
LicenseKey: key,
|
||||
Usage: usagesPayload,
|
||||
}
|
||||
|
||||
lm.UploadUsageWithExponentalBackOff(ctx, payload)
|
||||
}
|
||||
|
||||
|
||||
33
ee/types/featuretypes/enterprise.go
Normal file
33
ee/types/featuretypes/enterprise.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package featuretypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
var (
|
||||
SingleSignOn = featuretypes.MustNewName("SingleSignOn")
|
||||
)
|
||||
|
||||
func NewEnterpriseRegistry() (featuretypes.Registry, error) {
|
||||
enterpriseRegistry, err := featuretypes.NewRegistry(
|
||||
&featuretypes.Feature{
|
||||
Name: SingleSignOn,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Description: "Enable single sign on.",
|
||||
Stage: featuretypes.StageStable,
|
||||
Default: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enterpriseRegistry.MergeOrOverride(featuretypes.MustNewCommunityRegistry()), nil
|
||||
}
|
||||
|
||||
func MustNewEnterpriseRegistry() featuretypes.Registry {
|
||||
enterpriseRegistry, err := NewEnterpriseRegistry()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return enterpriseRegistry
|
||||
}
|
||||
1
ee/types/licensetypes/feature.go
Normal file
1
ee/types/licensetypes/feature.go
Normal file
@@ -0,0 +1 @@
|
||||
package licensetypes
|
||||
245
ee/types/licensetypes/license.go
Normal file
245
ee/types/licensetypes/license.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package licensetypes
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
type License struct {
|
||||
ID string
|
||||
Key string
|
||||
Contents map[string]any
|
||||
OrgFeatures []*featuretypes.StorableOrgFeature
|
||||
}
|
||||
|
||||
// type License struct {
|
||||
// Key string `json:"key" db:"key"`
|
||||
// ActivationId string `json:"activationId" db:"activationId"`
|
||||
// CreatedAt time.Time `db:"created_at"`
|
||||
|
||||
// // PlanDetails contains the encrypted plan info
|
||||
// PlanDetails string `json:"planDetails" db:"planDetails"`
|
||||
|
||||
// // stores parsed license details
|
||||
// LicensePlan
|
||||
|
||||
// FeatureSet basemodel.FeatureSet
|
||||
|
||||
// // populated in case license has any errors
|
||||
// ValidationMessage string `db:"validationMessage"`
|
||||
|
||||
// // used only for sending details to front-end
|
||||
// IsCurrent bool `json:"isCurrent"`
|
||||
// }
|
||||
|
||||
// func (l *License) MarshalJSON() ([]byte, error) {
|
||||
|
||||
// return json.Marshal(&struct {
|
||||
// Key string `json:"key" db:"key"`
|
||||
// ActivationId string `json:"activationId" db:"activationId"`
|
||||
// ValidationMessage string `db:"validationMessage"`
|
||||
// IsCurrent bool `json:"isCurrent"`
|
||||
// PlanKey string `json:"planKey"`
|
||||
// ValidFrom time.Time `json:"ValidFrom"`
|
||||
// ValidUntil time.Time `json:"ValidUntil"`
|
||||
// Status string `json:"status"`
|
||||
// }{
|
||||
// Key: l.Key,
|
||||
// ActivationId: l.ActivationId,
|
||||
// IsCurrent: l.IsCurrent,
|
||||
// PlanKey: l.PlanKey,
|
||||
// ValidFrom: time.Unix(l.ValidFrom, 0),
|
||||
// ValidUntil: time.Unix(l.ValidUntil, 0),
|
||||
// Status: l.Status,
|
||||
// ValidationMessage: l.ValidationMessage,
|
||||
// })
|
||||
// }
|
||||
|
||||
// type LicensePlan struct {
|
||||
// PlanKey string `json:"planKey"`
|
||||
// ValidFrom int64 `json:"validFrom"`
|
||||
// ValidUntil int64 `json:"validUntil"`
|
||||
// Status string `json:"status"`
|
||||
// }
|
||||
|
||||
// type Licenses struct {
|
||||
// TrialStart int64 `json:"trialStart"`
|
||||
// TrialEnd int64 `json:"trialEnd"`
|
||||
// OnTrial bool `json:"onTrial"`
|
||||
// WorkSpaceBlock bool `json:"workSpaceBlock"`
|
||||
// TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"`
|
||||
// GracePeriodEnd int64 `json:"gracePeriodEnd"`
|
||||
// Licenses []License `json:"licenses"`
|
||||
// }
|
||||
|
||||
// type SubscriptionServerResp struct {
|
||||
// Status string `json:"status"`
|
||||
// Data Licenses `json:"data"`
|
||||
// }
|
||||
|
||||
// type Plan struct {
|
||||
// Name string `json:"name"`
|
||||
// }
|
||||
|
||||
// type LicenseDB struct {
|
||||
// ID string `json:"id"`
|
||||
// Key string `json:"key"`
|
||||
// Data string `json:"data"`
|
||||
// }
|
||||
// type LicenseV3 struct {
|
||||
// ID string
|
||||
// Key string
|
||||
// Data map[string]interface{}
|
||||
// PlanName string
|
||||
// Features basemodel.FeatureSet
|
||||
// Status string
|
||||
// IsCurrent bool
|
||||
// ValidFrom int64
|
||||
// ValidUntil int64
|
||||
// }
|
||||
|
||||
// func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
|
||||
// var zeroValue T
|
||||
// if val, ok := data[key]; ok {
|
||||
// if value, ok := val.(T); ok {
|
||||
// return value, nil
|
||||
// }
|
||||
// return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
|
||||
// }
|
||||
// return zeroValue, fmt.Errorf("%s key is missing", key)
|
||||
// }
|
||||
|
||||
// func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
||||
// var features basemodel.FeatureSet
|
||||
|
||||
// // extract id from data
|
||||
// licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// delete(data, "id")
|
||||
|
||||
// // extract key from data
|
||||
// licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// delete(data, "key")
|
||||
|
||||
// // extract status from data
|
||||
// status, err := extractKeyFromMapStringInterface[string](data, "status")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// // if license status is invalid then default it to basic
|
||||
// if status == LicenseStatusInvalid {
|
||||
// planName = PlanNameBasic
|
||||
// }
|
||||
|
||||
// featuresFromZeus := basemodel.FeatureSet{}
|
||||
// if _features, ok := data["features"]; ok {
|
||||
// featuresData, err := json.Marshal(_features)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "failed to marshal features data")
|
||||
// }
|
||||
|
||||
// if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
||||
// return nil, errors.Wrap(err, "failed to unmarshal features data")
|
||||
// }
|
||||
// }
|
||||
|
||||
// switch planName {
|
||||
// case PlanNameTeams:
|
||||
// features = append(features, ProPlan...)
|
||||
// case PlanNameEnterprise:
|
||||
// features = append(features, EnterprisePlan...)
|
||||
// case PlanNameBasic:
|
||||
// features = append(features, BasicPlan...)
|
||||
// default:
|
||||
// features = append(features, BasicPlan...)
|
||||
// }
|
||||
|
||||
// if len(featuresFromZeus) > 0 {
|
||||
// for _, feature := range featuresFromZeus {
|
||||
// exists := false
|
||||
// for i, existingFeature := range features {
|
||||
// if existingFeature.Name == feature.Name {
|
||||
// features[i] = feature // Replace existing feature
|
||||
// exists = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if !exists {
|
||||
// features = append(features, feature) // Append if it doesn't exist
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// data["features"] = features
|
||||
|
||||
// _validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
|
||||
// if err != nil {
|
||||
// _validFrom = 0
|
||||
// }
|
||||
// validFrom := int64(_validFrom)
|
||||
|
||||
// _validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
|
||||
// if err != nil {
|
||||
// _validUntil = 0
|
||||
// }
|
||||
// validUntil := int64(_validUntil)
|
||||
|
||||
// return &LicenseV3{
|
||||
// ID: licenseID,
|
||||
// Key: licenseKey,
|
||||
// Data: data,
|
||||
// PlanName: planName,
|
||||
// Features: features,
|
||||
// ValidFrom: validFrom,
|
||||
// ValidUntil: validUntil,
|
||||
// Status: status,
|
||||
// }, nil
|
||||
|
||||
// }
|
||||
|
||||
// func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
|
||||
// licenseDataWithIdAndKey := data
|
||||
// licenseDataWithIdAndKey["id"] = id
|
||||
// licenseDataWithIdAndKey["key"] = key
|
||||
// return NewLicenseV3(licenseDataWithIdAndKey)
|
||||
// }
|
||||
|
||||
// func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
|
||||
// planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
|
||||
// if !ok {
|
||||
// planKeyFromPlanName = Basic
|
||||
// }
|
||||
// return &License{
|
||||
// Key: l.Key,
|
||||
// ActivationId: "",
|
||||
// PlanDetails: "",
|
||||
// FeatureSet: l.Features,
|
||||
// ValidationMessage: "",
|
||||
// IsCurrent: l.IsCurrent,
|
||||
// LicensePlan: LicensePlan{
|
||||
// PlanKey: planKeyFromPlanName,
|
||||
// ValidFrom: l.ValidFrom,
|
||||
// ValidUntil: l.ValidUntil,
|
||||
// Status: l.Status},
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// type CheckoutRequest struct {
|
||||
// SuccessURL string `json:"url"`
|
||||
// }
|
||||
|
||||
// type PortalRequest struct {
|
||||
// SuccessURL string `json:"url"`
|
||||
// }
|
||||
160
ee/types/licensetypes/license_test.go
Normal file
160
ee/types/licensetypes/license_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package licensetypes
|
||||
|
||||
// func TestNewLicenseV3(t *testing.T) {
|
||||
// testCases := []struct {
|
||||
// name string
|
||||
// data []byte
|
||||
// pass bool
|
||||
// expected *LicenseV3
|
||||
// error error
|
||||
// }{
|
||||
// {
|
||||
// name: "Error for missing license id",
|
||||
// data: []byte(`{}`),
|
||||
// pass: false,
|
||||
// error: errors.New("id key is missing"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for license id not being a valid string",
|
||||
// data: []byte(`{"id": 10}`),
|
||||
// pass: false,
|
||||
// error: errors.New("id key is not a valid string"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for missing license key",
|
||||
// data: []byte(`{"id":"does-not-matter"}`),
|
||||
// pass: false,
|
||||
// error: errors.New("key key is missing"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for invalid string license key",
|
||||
// data: []byte(`{"id":"does-not-matter","key":10}`),
|
||||
// pass: false,
|
||||
// error: errors.New("key key is not a valid string"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for missing license status",
|
||||
// data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
|
||||
// pass: false,
|
||||
// error: errors.New("status key is missing"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for invalid string license status",
|
||||
// data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
|
||||
// pass: false,
|
||||
// error: errors.New("status key is not a valid string"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for missing license plan",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
|
||||
// pass: false,
|
||||
// error: errors.New("plan key is missing"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for invalid json license plan",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
|
||||
// pass: false,
|
||||
// error: errors.New("plan key is not a valid map[string]interface {}"),
|
||||
// },
|
||||
// {
|
||||
// name: "Error for invalid license plan",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
|
||||
// pass: false,
|
||||
// error: errors.New("name key is missing"),
|
||||
// },
|
||||
// {
|
||||
// name: "Parse the entire license properly",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
// pass: true,
|
||||
// expected: &LicenseV3{
|
||||
// ID: "does-not-matter",
|
||||
// Key: "does-not-matter-key",
|
||||
// Data: map[string]interface{}{
|
||||
// "plan": map[string]interface{}{
|
||||
// "name": "TEAMS",
|
||||
// },
|
||||
// "category": "FREE",
|
||||
// "status": "ACTIVE",
|
||||
// "valid_from": float64(1730899309),
|
||||
// "valid_until": float64(-1),
|
||||
// },
|
||||
// PlanName: PlanNameTeams,
|
||||
// ValidFrom: 1730899309,
|
||||
// ValidUntil: -1,
|
||||
// Status: "ACTIVE",
|
||||
// IsCurrent: false,
|
||||
// Features: model.FeatureSet{},
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "Fallback to basic plan if license status is invalid",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||
// pass: true,
|
||||
// expected: &LicenseV3{
|
||||
// ID: "does-not-matter",
|
||||
// Key: "does-not-matter-key",
|
||||
// Data: map[string]interface{}{
|
||||
// "plan": map[string]interface{}{
|
||||
// "name": "TEAMS",
|
||||
// },
|
||||
// "category": "FREE",
|
||||
// "status": "INVALID",
|
||||
// "valid_from": float64(1730899309),
|
||||
// "valid_until": float64(-1),
|
||||
// },
|
||||
// PlanName: PlanNameBasic,
|
||||
// ValidFrom: 1730899309,
|
||||
// ValidUntil: -1,
|
||||
// Status: "INVALID",
|
||||
// IsCurrent: false,
|
||||
// Features: model.FeatureSet{},
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "fallback states for validFrom and validUntil",
|
||||
// data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||
// pass: true,
|
||||
// expected: &LicenseV3{
|
||||
// ID: "does-not-matter",
|
||||
// Key: "does-not-matter-key",
|
||||
// Data: map[string]interface{}{
|
||||
// "plan": map[string]interface{}{
|
||||
// "name": "TEAMS",
|
||||
// },
|
||||
// "valid_from": 1234.456,
|
||||
// "valid_until": 5678.567,
|
||||
// "category": "FREE",
|
||||
// "status": "ACTIVE",
|
||||
// },
|
||||
// PlanName: PlanNameTeams,
|
||||
// ValidFrom: 1234,
|
||||
// ValidUntil: 5678,
|
||||
// Status: "ACTIVE",
|
||||
// IsCurrent: false,
|
||||
// Features: model.FeatureSet{},
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
|
||||
// for _, tc := range testCases {
|
||||
// var licensePayload map[string]interface{}
|
||||
// err := json.Unmarshal(tc.data, &licensePayload)
|
||||
// require.NoError(t, err)
|
||||
// license, err := NewLicenseV3(licensePayload)
|
||||
// if license != nil {
|
||||
// license.Features = make(model.FeatureSet, 0)
|
||||
// delete(license.Data, "features")
|
||||
// }
|
||||
|
||||
// if tc.pass {
|
||||
// require.NoError(t, err)
|
||||
// require.NotNil(t, license)
|
||||
// assert.Equal(t, tc.expected, license)
|
||||
// } else {
|
||||
// require.Error(t, err)
|
||||
// assert.EqualError(t, err, tc.error.Error())
|
||||
// require.Nil(t, license)
|
||||
// }
|
||||
|
||||
// }
|
||||
// }
|
||||
31
ee/zeus/config.go
Normal file
31
ee/zeus/config.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package zeus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
neturl "net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
// This will be set via ldflags at build time.
|
||||
var (
|
||||
url string = "<unset>"
|
||||
once sync.Once
|
||||
GlobalConfig zeus.Config
|
||||
)
|
||||
|
||||
// init initializes and validates the Zeus configuration
|
||||
func init() {
|
||||
once.Do(func() {
|
||||
parsedURL, err := neturl.Parse(url)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid zeus URL: %w", err))
|
||||
}
|
||||
|
||||
GlobalConfig = zeus.Config{URL: parsedURL}
|
||||
if err := GlobalConfig.Validate(); err != nil {
|
||||
panic(fmt.Errorf("invalid zeus config: %w", err))
|
||||
}
|
||||
})
|
||||
}
|
||||
61
ee/zeus/implzeus/provider.go
Normal file
61
ee/zeus/implzeus/provider.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package implzeus
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/http/client"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metertypes"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
settings factory.ScopedProviderSettings
|
||||
config zeus.Config
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
func NewProviderFactory() factory.ProviderFactory[zeus.Zeus, zeus.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("impl"), func(ctx context.Context, providerSettings factory.ProviderSettings, config zeus.Config) (zeus.Zeus, error) {
|
||||
return New(ctx, providerSettings, config)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config zeus.Config) (zeus.Zeus, error) {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/zeus/implzeus")
|
||||
|
||||
httpClient := client.New(
|
||||
settings.Logger(),
|
||||
providerSettings.TracerProvider,
|
||||
providerSettings.MeterProvider,
|
||||
client.WithRequestResponseLog(true),
|
||||
client.WithRetryCount(3),
|
||||
)
|
||||
|
||||
return &Provider{
|
||||
settings: settings,
|
||||
config: config,
|
||||
client: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetLicense(ctx context.Context, key string) (*licensetypes.License, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetCheckoutURL(ctx context.Context, key string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetPortalURL(ctx context.Context, key string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetDeployment(ctx context.Context, key string) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) PutMeters(ctx context.Context, key string, meters metertypes.Meters) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,26 +1,12 @@
|
||||
// keep this consistent with backend constants.go
|
||||
export enum FeatureKeys {
|
||||
SSO = 'SSO',
|
||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
||||
BASIC_PLAN = 'BASIC_PLAN',
|
||||
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
|
||||
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
|
||||
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
|
||||
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
|
||||
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
|
||||
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
|
||||
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
|
||||
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
|
||||
OSS = 'OSS',
|
||||
ONBOARDING = 'ONBOARDING',
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
GATEWAY = 'GATEWAY',
|
||||
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
|
||||
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
|
||||
ANOMALY_DETECTION = 'ANOMALY_DETECTION',
|
||||
AWS_INTEGRATION = 'AWS_INTEGRATION',
|
||||
ONBOARDING_V3 = 'ONBOARDING_V3',
|
||||
THIRD_PARTY_API = 'THIRD_PARTY_API',
|
||||
TRACE_FUNNELS = 'TRACE_FUNNELS',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ChannelType,
|
||||
@@ -11,11 +10,8 @@ import {
|
||||
WebhookChannel,
|
||||
} from 'container/CreateAlertChannels/config';
|
||||
import history from 'lib/history';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dispatch, ReactElement, SetStateAction } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||
import { isFeatureKeys } from 'utils/app';
|
||||
|
||||
import EmailSettings from './Settings/Email';
|
||||
import MsTeamsSettings from './Settings/MsTeams';
|
||||
@@ -39,17 +35,6 @@ function FormAlertChannels({
|
||||
editing = false,
|
||||
}: FormAlertChannelsProps): JSX.Element {
|
||||
const { t } = useTranslation('channels');
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
|
||||
|
||||
const featureKey = isFeatureKeys(feature)
|
||||
? feature
|
||||
: FeatureKeys.ALERT_CHANNEL_SLACK;
|
||||
|
||||
const hasFeature = featureFlags?.find(
|
||||
(flag: FeatureFlagProps) => flag.name === featureKey,
|
||||
);
|
||||
|
||||
const renderSettings = (): ReactElement | null => {
|
||||
switch (type) {
|
||||
@@ -146,7 +131,7 @@ function FormAlertChannels({
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={savingState || !hasFeature}
|
||||
disabled={savingState}
|
||||
loading={savingState}
|
||||
type="primary"
|
||||
onClick={(): void => onSaveHandler(type)}
|
||||
@@ -154,7 +139,7 @@ function FormAlertChannels({
|
||||
{t('button_save_channel')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={testingState || !hasFeature}
|
||||
disabled={testingState}
|
||||
loading={testingState}
|
||||
onClick={(): void => onTestHandler(type)}
|
||||
>
|
||||
|
||||
@@ -467,10 +467,6 @@ function FormAlertRules({
|
||||
panelType,
|
||||
]);
|
||||
|
||||
const isAlertAvailable =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_ALERTS)
|
||||
?.active || false;
|
||||
|
||||
const saveRule = useCallback(async () => {
|
||||
if (!isFormValid()) {
|
||||
return;
|
||||
@@ -689,7 +685,6 @@ function FormAlertRules({
|
||||
const isAlertNameMissing = !formInstance.getFieldValue('alert');
|
||||
|
||||
const isAlertAvailableToSave =
|
||||
isAlertAvailable &&
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
alertType !== AlertTypes.METRICS_BASED_ALERT;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { WarningOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Modal, Space, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
initialQueriesMap,
|
||||
@@ -27,7 +26,6 @@ import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
|
||||
import { Check, X } from 'lucide-react';
|
||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||
import {
|
||||
getNextWidgets,
|
||||
@@ -79,8 +77,6 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
|
||||
const { t } = useTranslation(['dashboard']);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
|
||||
const {
|
||||
@@ -566,12 +562,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const isQueryBuilderActive =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_PANELS)
|
||||
?.active || false;
|
||||
|
||||
const isNewTraceLogsAvailable =
|
||||
isQueryBuilderActive &&
|
||||
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
|
||||
currentQuery.builder.queryData.find(
|
||||
(query) => query.dataSource !== DataSource.METRICS,
|
||||
|
||||
@@ -13,11 +13,7 @@ function OrganizationSettings(): JSX.Element {
|
||||
const isNotSSO =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const isNoUpSell =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.DISABLE_UPSELL)
|
||||
?.active || false;
|
||||
|
||||
const isAuthDomain = !isNoUpSell || (isNoUpSell && !isNotSSO);
|
||||
const isAuthDomain = !isNotSSO;
|
||||
|
||||
if (!org) {
|
||||
return <div />;
|
||||
|
||||
@@ -186,76 +186,6 @@ export function getAppContextMock(
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.OSS,
|
||||
active: false,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.DISABLE_UPSELL,
|
||||
active: false,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.CUSTOM_METRICS_FUNCTION,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.QUERY_BUILDER_PANELS,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.QUERY_BUILDER_ALERTS,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.ALERT_CHANNEL_SLACK,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.ALERT_CHANNEL_WEBHOOK,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.ALERT_CHANNEL_PAGERDUTY,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.ALERT_CHANNEL_OPSGENIE,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.ALERT_CHANNEL_MSTEAMS,
|
||||
active: true,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
{
|
||||
name: FeatureKeys.USE_SPAN_METRICS,
|
||||
active: false,
|
||||
|
||||
1
go.mod
1
go.mod
@@ -181,6 +181,7 @@ require (
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/open-feature/go-sdk v1.14.1 // indirect
|
||||
github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.111.0 // indirect
|
||||
github.com/paulmach/orb v0.11.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -717,6 +717,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=
|
||||
|
||||
1
pkg/flagger/api.go
Normal file
1
pkg/flagger/api.go
Normal file
@@ -0,0 +1 @@
|
||||
package flagger
|
||||
35
pkg/flagger/config.go
Normal file
35
pkg/flagger/config.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package flagger
|
||||
|
||||
import "github.com/SigNoz/signoz/pkg/factory"
|
||||
|
||||
var _ factory.Config = Config{}
|
||||
|
||||
type Config struct {
|
||||
Provider string `json:"provider"`
|
||||
Memory Memory `json:"memory"`
|
||||
}
|
||||
|
||||
type Memory struct {
|
||||
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",
|
||||
Memory: Memory{},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
27
pkg/flagger/flagger.go
Normal file
27
pkg/flagger/flagger.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package flagger
|
||||
|
||||
import (
|
||||
"github.com/open-feature/go-sdk/openfeature"
|
||||
)
|
||||
|
||||
type Provider = openfeature.FeatureProvider
|
||||
|
||||
type FlaggerHook = openfeature.Hook
|
||||
|
||||
type Flagger = openfeature.IClient
|
||||
|
||||
type flagger struct {
|
||||
*openfeature.Client
|
||||
}
|
||||
|
||||
func New(provider Provider, hooks ...FlaggerHook) (Flagger, error) {
|
||||
client := openfeature.NewClient("signoz")
|
||||
|
||||
if err := openfeature.SetNamedProviderAndWait("signoz", provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client.AddHooks(hooks...)
|
||||
|
||||
return &flagger{Client: client}, nil
|
||||
}
|
||||
241
pkg/flagger/memoryprovider/provider.go
Normal file
241
pkg/flagger/memoryprovider/provider.go
Normal file
@@ -0,0 +1,241 @@
|
||||
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
|
||||
featureValues map[featuretypes.Name]featuretypes.FeatureValue
|
||||
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")
|
||||
|
||||
featureValues := make(map[featuretypes.Name]featuretypes.FeatureValue)
|
||||
for _, flag := range config.Memory.Boolean.Enabled {
|
||||
name, err := featuretypes.NewName(flag)
|
||||
if err != nil {
|
||||
settings.Logger().Error("invalid flag name encountered", "flag", flag, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
featureValues[name] = featuretypes.FeatureValue{
|
||||
Name: name,
|
||||
Variant: featuretypes.KindBooleanVariantEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
for _, flag := range config.Memory.Boolean.Disabled {
|
||||
name, err := featuretypes.NewName(flag)
|
||||
if err != nil {
|
||||
settings.Logger().Error("invalid flag name encountered", "flag", flag, "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := featureValues[name]; ok {
|
||||
settings.Logger().Error("flag already exists and has been enabled", "flag", flag)
|
||||
continue
|
||||
}
|
||||
|
||||
featureValues[name] = featuretypes.FeatureValue{
|
||||
Name: name,
|
||||
Variant: featuretypes.KindBooleanVariantDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
return &provider{
|
||||
config: config,
|
||||
settings: settings,
|
||||
featureValues: featureValues,
|
||||
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.Get(flag)
|
||||
if err != nil {
|
||||
return openfeature.BoolResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureValues[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetVariantValue[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.Get(flag)
|
||||
if err != nil {
|
||||
return openfeature.StringResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureValues[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetVariantValue[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.Get(flag)
|
||||
if err != nil {
|
||||
return openfeature.FloatResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureValues[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetVariantValue[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.Get(flag)
|
||||
if err != nil {
|
||||
return openfeature.IntResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureValues[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetVariantValue[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.Get(flag)
|
||||
if err != nil {
|
||||
return openfeature.InterfaceResolutionDetail{
|
||||
Value: defaultValue,
|
||||
ProviderResolutionDetail: detail,
|
||||
}
|
||||
}
|
||||
|
||||
if featureValue, ok := provider.featureValues[feature.Name]; ok {
|
||||
value, detail, err := featuretypes.GetVariantValue[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{}
|
||||
}
|
||||
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("UseTracesNewSchema")
|
||||
FeatureUseLogsNewSchema = featuretypes.MustNewName("UseLogsNewSchema")
|
||||
)
|
||||
|
||||
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.NewBooleanFeatureVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureUseLogsNewSchema,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Description: "Use new logs schema.",
|
||||
Stage: featuretypes.StageStable,
|
||||
DefaultVariant: featuretypes.KindBooleanVariantDisabled,
|
||||
Variants: featuretypes.NewBooleanFeatureVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
@@ -17,7 +17,7 @@ type Client struct {
|
||||
netc *http.Client
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider metric.MeterProvider, opts ...Option) (*Client, error) {
|
||||
func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider metric.MeterProvider, opts ...Option) *Client {
|
||||
clientOpts := options{
|
||||
retryCount: 3,
|
||||
requestResponseLog: false,
|
||||
@@ -46,7 +46,7 @@ func New(logger *slog.Logger, tracerProvider trace.TracerProvider, meterProvider
|
||||
return &Client{
|
||||
netc: netc,
|
||||
c: c,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Do(request *http.Request) (*http.Response, error) {
|
||||
|
||||
16
pkg/licensing/api.go
Normal file
16
pkg/licensing/api.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package licensing
|
||||
|
||||
// type API interface {
|
||||
// GetLicenses(context.Context, valuer.UUID, licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error)
|
||||
// }
|
||||
|
||||
// func Get(){
|
||||
// //
|
||||
// return 501
|
||||
// }
|
||||
|
||||
// func Put() {
|
||||
// //400 bad request
|
||||
|
||||
// //501
|
||||
// }
|
||||
17
pkg/licensing/config.go
Normal file
17
pkg/licensing/config.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package licensing
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
PollingConfig PollingConfig `mapstructure:"polling"`
|
||||
}
|
||||
|
||||
type PollingConfig struct {
|
||||
Interval time.Duration `mapstructure:"interval"`
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
27
pkg/licensing/licensing.go
Normal file
27
pkg/licensing/licensing.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package licensing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeLicensingServerNotFound = errors.MustNewCode("licensing_server_not_found")
|
||||
)
|
||||
|
||||
type Licensing interface {
|
||||
factory.Service
|
||||
|
||||
// GetLicenses gets the licenses for the organization.
|
||||
GetLicenses(context.Context, valuer.UUID, licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error)
|
||||
|
||||
// GetLatestLicense gets the latest license for the organization.
|
||||
GetLatestLicense(context.Context, valuer.UUID) (licensetypes.License, error)
|
||||
|
||||
// SetLicense sets the license for the organization.
|
||||
SetLicense(context.Context, valuer.UUID, string) error
|
||||
}
|
||||
50
pkg/licensing/nooplicensing/provider.go
Normal file
50
pkg/licensing/nooplicensing/provider.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package nooplicensing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
stopC chan struct{}
|
||||
license licensetypes.License
|
||||
}
|
||||
|
||||
func NewFactory() factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||
return New(ctx, providerSettings, config)
|
||||
})
|
||||
}
|
||||
|
||||
func New(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||
return &provider{
|
||||
stopC: make(chan struct{}),
|
||||
license: licensetypes.NewNoop(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Start(ctx context.Context) error {
|
||||
<-provider.stopC
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetLicenses(ctx context.Context, orgID valuer.UUID, params licensetypes.GettableLicenseParams) (licensetypes.GettableLicenses, error) {
|
||||
return licensetypes.GettableLicenses{provider.license}, nil
|
||||
}
|
||||
|
||||
func (provider *provider) GetLatestLicense(ctx context.Context, orgID valuer.UUID) (licensetypes.License, error) {
|
||||
return provider.license, nil
|
||||
}
|
||||
|
||||
func (provider *provider) SetLicense(ctx context.Context, orgID valuer.UUID, key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (provider *provider) Stop(ctx context.Context) error {
|
||||
close(provider.stopC)
|
||||
return nil
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -143,9 +144,6 @@ type ClickHouseReader struct {
|
||||
liveTailRefreshSeconds int
|
||||
cluster string
|
||||
|
||||
useLogsNewSchema bool
|
||||
useTraceNewSchema bool
|
||||
|
||||
logsTableName string
|
||||
logsLocalTableName string
|
||||
|
||||
@@ -166,8 +164,6 @@ func NewReader(
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
cluster string,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool,
|
||||
fluxIntervalForTraceDetail time.Duration,
|
||||
cache cache.Cache,
|
||||
) *ClickHouseReader {
|
||||
@@ -181,13 +177,13 @@ func NewReaderFromClickhouseConnection(
|
||||
telemetryStore telemetrystore.TelemetryStore,
|
||||
prometheus prometheus.Prometheus,
|
||||
cluster string,
|
||||
useLogsNewSchema bool,
|
||||
useTraceNewSchema bool,
|
||||
fluxIntervalForTraceDetail time.Duration,
|
||||
cache cache.Cache,
|
||||
) *ClickHouseReader {
|
||||
logsTableName := options.primary.LogsTable
|
||||
logsLocalTableName := options.primary.LogsLocalTable
|
||||
|
||||
useLogsNewSchema, _ := featureControl.Boolean(context.Background(), valuer.UUID{}, featuretypes.UseLogsNewSchema)
|
||||
if useLogsNewSchema {
|
||||
logsTableName = options.primary.LogsTableV2
|
||||
logsLocalTableName = options.primary.LogsLocalTableV2
|
||||
@@ -195,6 +191,7 @@ func NewReaderFromClickhouseConnection(
|
||||
|
||||
traceTableName := options.primary.IndexTable
|
||||
traceLocalTableName := options.primary.LocalIndexTable
|
||||
useTraceNewSchema, _ := featureControl.Boolean(context.Background(), valuer.UUID{}, featuretypes.UseTracesNewSchema)
|
||||
if useTraceNewSchema {
|
||||
traceTableName = options.primary.TraceIndexTableV3
|
||||
traceLocalTableName = options.primary.TraceLocalTableNameV3
|
||||
@@ -290,7 +287,7 @@ func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, erro
|
||||
services := []string{}
|
||||
query := fmt.Sprintf(`SELECT DISTINCT serviceName FROM %s.%s WHERE toDate(timestamp) > now() - INTERVAL 1 DAY`, r.TraceDB, r.traceTableName)
|
||||
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
query = fmt.Sprintf(`SELECT DISTINCT serviceName FROM %s.%s WHERE ts_bucket_start > (toUnixTimestamp(now() - INTERVAL 1 DAY) - 1800) AND toDate(timestamp) > now() - INTERVAL 1 DAY`, r.TraceDB, r.traceTableName)
|
||||
}
|
||||
|
||||
@@ -538,7 +535,7 @@ func (r *ClickHouseReader) GetServicesV2(ctx context.Context, queryParams *model
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams, skipConfig *model.SkipConfig) (*[]model.ServiceItem, *model.ApiError) {
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
return r.GetServicesV2(ctx, queryParams, skipConfig)
|
||||
}
|
||||
|
||||
@@ -892,7 +889,7 @@ func (r *ClickHouseReader) GetTopOperationsV2(ctx context.Context, queryParams *
|
||||
|
||||
func (r *ClickHouseReader) GetTopOperations(ctx context.Context, queryParams *model.GetTopOperationsParams) (*[]model.TopOperationsItem, *model.ApiError) {
|
||||
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
return r.GetTopOperationsV2(ctx, queryParams)
|
||||
}
|
||||
|
||||
@@ -2680,7 +2677,7 @@ func (r *ClickHouseReader) GetSpansInLastHeartBeatInterval(ctx context.Context,
|
||||
var spansInLastHeartBeatInterval uint64
|
||||
|
||||
queryStr := fmt.Sprintf("SELECT count() from %s.%s where timestamp > toUnixTimestamp(now()-toIntervalMinute(%d));", signozTraceDBName, signozSpansTable, int(interval.Minutes()))
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
queryStr = fmt.Sprintf("SELECT count() from %s.%s where ts_bucket_start >= toUInt64(toUnixTimestamp(now() - toIntervalMinute(%d))) - 1800 and timestamp > toUnixTimestamp(now()-toIntervalMinute(%d));", signozTraceDBName, r.traceTableName, int(interval.Minutes()), int(interval.Minutes()))
|
||||
}
|
||||
r.db.QueryRow(ctx, queryStr).Scan(&spansInLastHeartBeatInterval)
|
||||
@@ -2820,7 +2817,7 @@ func (r *ClickHouseReader) GetTagsInfoInLastHeartBeatInterval(ctx context.Contex
|
||||
where timestamp > toUnixTimestamp(now()-toIntervalMinute(%d))
|
||||
group by serviceName, env, language;`, r.TraceDB, r.traceTableName, int(interval.Minutes()))
|
||||
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
queryStr = fmt.Sprintf(`select serviceName, resources_string['deployment.environment'] as env,
|
||||
resources_string['telemetry.sdk.language'] as language from %s.%s
|
||||
where timestamp > toUnixTimestamp(now()-toIntervalMinute(%d))
|
||||
@@ -2925,8 +2922,9 @@ func (r *ClickHouseReader) extractSelectedAndInterestingFields(tableStatement st
|
||||
if overrideFieldType != "" {
|
||||
field.Type = overrideFieldType
|
||||
}
|
||||
ok, _ := r.featureControl.Boolean(context.Background(), valuer.UUID{}, featuretypes.UseLogsNewSchema)
|
||||
// all static fields are assumed to be selected as we don't allow changing them
|
||||
if isColumn(r.useLogsNewSchema, tableStatement, field.Type, field.Name, field.DataType) {
|
||||
if isColumn(ok, tableStatement, field.Type, field.Name, field.DataType) {
|
||||
response.Selected = append(response.Selected, field)
|
||||
} else {
|
||||
response.Interesting = append(response.Interesting, field)
|
||||
@@ -3007,7 +3005,8 @@ func (r *ClickHouseReader) UpdateLogField(ctx context.Context, field *model.Upda
|
||||
return &model.ApiError{Err: err, Typ: model.ErrorBadData}
|
||||
}
|
||||
|
||||
if r.useLogsNewSchema {
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseLogsNewSchema)
|
||||
if ok {
|
||||
return r.UpdateLogFieldV2(ctx, field)
|
||||
}
|
||||
|
||||
@@ -3896,6 +3895,7 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v
|
||||
var tagKey string
|
||||
var dataType string
|
||||
var attType string
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseLogsNewSchema)
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&tagKey, &attType, &dataType); err != nil {
|
||||
return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
|
||||
@@ -3904,7 +3904,7 @@ func (r *ClickHouseReader) GetLogAggregateAttributes(ctx context.Context, req *v
|
||||
Key: tagKey,
|
||||
DataType: v3.AttributeKeyDataType(dataType),
|
||||
Type: v3.AttributeKeyType(attType),
|
||||
IsColumn: isColumn(r.useLogsNewSchema, statements[0].Statement, attType, tagKey, dataType),
|
||||
IsColumn: isColumn(ok, statements[0].Statement, attType, tagKey, dataType),
|
||||
}
|
||||
response.AttributeKeys = append(response.AttributeKeys, key)
|
||||
}
|
||||
@@ -3950,6 +3950,7 @@ func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.Filt
|
||||
var attributeKey string
|
||||
var attributeDataType string
|
||||
var tagType string
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseLogsNewSchema)
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&attributeKey, &tagType, &attributeDataType); err != nil {
|
||||
return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
|
||||
@@ -3959,7 +3960,7 @@ func (r *ClickHouseReader) GetLogAttributeKeys(ctx context.Context, req *v3.Filt
|
||||
Key: attributeKey,
|
||||
DataType: v3.AttributeKeyDataType(attributeDataType),
|
||||
Type: v3.AttributeKeyType(tagType),
|
||||
IsColumn: isColumn(r.useLogsNewSchema, statements[0].Statement, tagType, attributeKey, attributeDataType),
|
||||
IsColumn: isColumn(ok, statements[0].Statement, tagType, attributeKey, attributeDataType),
|
||||
}
|
||||
|
||||
response.AttributeKeys = append(response.AttributeKeys, key)
|
||||
@@ -4690,7 +4691,8 @@ func (r *ClickHouseReader) GetTraceAggregateAttributes(ctx context.Context, req
|
||||
}
|
||||
|
||||
fields := constants.NewStaticFieldsTraces
|
||||
if !r.useTraceNewSchema {
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema)
|
||||
if !ok {
|
||||
fields = constants.DeprecatedStaticFieldsTraces
|
||||
}
|
||||
|
||||
@@ -4754,7 +4756,8 @@ func (r *ClickHouseReader) GetTraceAttributeKeys(ctx context.Context, req *v3.Fi
|
||||
|
||||
// remove this later just to have NewStaticFieldsTraces in the response
|
||||
fields := constants.NewStaticFieldsTraces
|
||||
if !r.useTraceNewSchema {
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema)
|
||||
if !ok {
|
||||
fields = constants.DeprecatedStaticFieldsTraces
|
||||
}
|
||||
|
||||
@@ -4818,7 +4821,7 @@ func (r *ClickHouseReader) GetTraceAttributeValues(ctx context.Context, req *v3.
|
||||
|
||||
// TODO(nitya): remove 24 hour limit in future after checking the perf/resource implications
|
||||
where := "timestamp >= toDateTime64(now() - INTERVAL 48 HOUR, 9)"
|
||||
if r.useTraceNewSchema {
|
||||
if ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema); ok {
|
||||
where += " AND ts_bucket_start >= toUInt64(toUnixTimestamp(now() - INTERVAL 48 HOUR))"
|
||||
}
|
||||
query = fmt.Sprintf("SELECT DISTINCT %s FROM %s.%s WHERE %s AND %s ILIKE $1 LIMIT $2", selectKey, r.TraceDB, r.traceTableName, where, filterValueColumnWhere)
|
||||
@@ -4916,7 +4919,8 @@ func (r *ClickHouseReader) GetSpanAttributeKeysV2(ctx context.Context) (map[stri
|
||||
}
|
||||
|
||||
func (r *ClickHouseReader) GetSpanAttributeKeys(ctx context.Context) (map[string]v3.AttributeKey, error) {
|
||||
if r.useTraceNewSchema {
|
||||
ok, _ := r.featureControl.Boolean(ctx, valuer.UUID{}, featuretypes.UseTracesNewSchema)
|
||||
if ok {
|
||||
return r.GetSpanAttributeKeysV2(ctx)
|
||||
}
|
||||
var query string
|
||||
|
||||
@@ -43,7 +43,6 @@ type querier struct {
|
||||
fluxInterval time.Duration
|
||||
|
||||
builder *queryBuilder.QueryBuilder
|
||||
|
||||
// used for testing
|
||||
// TODO(srikanthccv): remove this once we have a proper mock
|
||||
testingMode bool
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
metricsV3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/cache"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -46,8 +45,7 @@ type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelTy
|
||||
type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, options metricsV3.Options) (string, error)
|
||||
|
||||
type QueryBuilder struct {
|
||||
options QueryBuilderOptions
|
||||
featureFlags interfaces.FeatureLookup
|
||||
options QueryBuilderOptions
|
||||
}
|
||||
|
||||
type QueryBuilderOptions struct {
|
||||
|
||||
@@ -68,10 +68,6 @@ func UseMetricsPreAggregation() bool {
|
||||
return GetOrDefaultEnv("USE_METRICS_PRE_AGGREGATION", "true") == "true"
|
||||
}
|
||||
|
||||
func EnableHostsInfraMonitoring() bool {
|
||||
return GetOrDefaultEnv("ENABLE_INFRA_METRICS", "true") == "true"
|
||||
}
|
||||
|
||||
var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false")
|
||||
|
||||
var DEFAULT_FEATURE_SET = model.FeatureSet{
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package featureManager
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type FeatureManager struct {
|
||||
}
|
||||
|
||||
func StartManager() *FeatureManager {
|
||||
fM := &FeatureManager{}
|
||||
return fM
|
||||
}
|
||||
|
||||
// CheckFeature will be internally used by backend routines
|
||||
// for feature gating
|
||||
func (fm *FeatureManager) CheckFeature(featureKey string) error {
|
||||
|
||||
feature, err := fm.GetFeatureFlag(featureKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if feature.Active {
|
||||
return nil
|
||||
}
|
||||
|
||||
return model.ErrFeatureUnavailable{Key: featureKey}
|
||||
}
|
||||
|
||||
// GetFeatureFlags returns current features
|
||||
func (fm *FeatureManager) GetFeatureFlags() (model.FeatureSet, error) {
|
||||
features := append(constants.DEFAULT_FEATURE_SET, model.Feature{
|
||||
Name: model.OSS,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func (fm *FeatureManager) InitFeatures(req model.FeatureSet) error {
|
||||
zap.L().Error("InitFeatures not implemented in OSS")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fm *FeatureManager) UpdateFeatureFlag(req model.Feature) error {
|
||||
zap.L().Error("UpdateFeatureFlag not implemented in OSS")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fm *FeatureManager) GetFeatureFlag(key string) (model.Feature, error) {
|
||||
features, err := fm.GetFeatureFlags()
|
||||
if err != nil {
|
||||
return model.Feature{}, err
|
||||
}
|
||||
for _, feature := range features {
|
||||
if feature.Name == key {
|
||||
return feature, nil
|
||||
}
|
||||
}
|
||||
return model.Feature{}, model.ErrFeatureUnavailable{Key: key}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package interfaces
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
type FeatureLookup interface {
|
||||
CheckFeature(f string) error
|
||||
GetFeatureFlags() (model.FeatureSet, error)
|
||||
GetFeatureFlag(f string) (model.Feature, error)
|
||||
UpdateFeatureFlag(features model.Feature) error
|
||||
InitFeatures(features model.FeatureSet) error
|
||||
}
|
||||
@@ -9,58 +9,11 @@ type Feature struct {
|
||||
Route string `db:"route" json:"route"`
|
||||
}
|
||||
|
||||
const CustomMetricsFunction = "CUSTOM_METRICS_FUNCTION"
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const OSS = "OSS"
|
||||
const QueryBuilderPanels = "QUERY_BUILDER_PANELS"
|
||||
const QueryBuilderAlerts = "QUERY_BUILDER_ALERTS"
|
||||
const UseSpanMetrics = "USE_SPAN_METRICS"
|
||||
const AlertChannelSlack = "ALERT_CHANNEL_SLACK"
|
||||
const AlertChannelWebhook = "ALERT_CHANNEL_WEBHOOK"
|
||||
const AlertChannelPagerduty = "ALERT_CHANNEL_PAGERDUTY"
|
||||
const AlertChannelMsTeams = "ALERT_CHANNEL_MSTEAMS"
|
||||
const AlertChannelOpsgenie = "ALERT_CHANNEL_OPSGENIE"
|
||||
const AlertChannelEmail = "ALERT_CHANNEL_EMAIL"
|
||||
const AnomalyDetection = "ANOMALY_DETECTION"
|
||||
const HostsInfraMonitoring = "HOSTS_INFRA_MONITORING"
|
||||
const TraceFunnels = "TRACE_FUNNELS"
|
||||
|
||||
var BasicPlan = FeatureSet{
|
||||
Feature{
|
||||
Name: OSS,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: DisableUpsell,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: CustomMetricsFunction,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: QueryBuilderPanels,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: QueryBuilderAlerts,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: UseSpanMetrics,
|
||||
Active: false,
|
||||
@@ -68,48 +21,6 @@ var BasicPlan = FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelSlack,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelWebhook,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelPagerduty,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelEmail,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AlertChannelMsTeams,
|
||||
Active: false,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
Feature{
|
||||
Name: AnomalyDetection,
|
||||
Active: false,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/featurecontrol"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/web"
|
||||
)
|
||||
@@ -25,6 +27,8 @@ type SigNoz struct {
|
||||
TelemetryStore telemetrystore.TelemetryStore
|
||||
Prometheus prometheus.Prometheus
|
||||
Alertmanager alertmanager.Alertmanager
|
||||
Zeus zeus.Zeus
|
||||
FeatureControl featurecontrol.FeatureControl
|
||||
}
|
||||
|
||||
func New(
|
||||
|
||||
@@ -66,7 +66,7 @@ func (migration *updatePatAndOrgDomains) Up(ctx context.Context, db *bun.DB) err
|
||||
}
|
||||
}
|
||||
|
||||
if err := updateOrgId(ctx, tx, "org_domains"); err != nil {
|
||||
if err := updateOrgId(ctx, tx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (migration *updatePatAndOrgDomains) Down(ctx context.Context, db *bun.DB) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateOrgId(ctx context.Context, tx bun.Tx, table string) error {
|
||||
func updateOrgId(ctx context.Context, tx bun.Tx) error {
|
||||
if _, err := tx.NewCreateTable().
|
||||
Model(&struct {
|
||||
bun.BaseModel `bun:"table:org_domains_new"`
|
||||
|
||||
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 = "enabled"
|
||||
KindBooleanVariantDisabled = "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. Inspired from https://github.com/open-feature/go-sdk/blob/main/openfeature/interfaces.go
|
||||
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 }
|
||||
78
pkg/types/featuretypes/feature.go
Normal file
78
pkg/types/featuretypes/feature.go
Normal file
@@ -0,0 +1,78 @@
|
||||
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 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:"default_variant"`
|
||||
|
||||
// Variants is the variants of the feature flag.
|
||||
Variants map[string]any `json:"variants"`
|
||||
}
|
||||
|
||||
type FeatureValue struct {
|
||||
// Name is the name of the feature flag.
|
||||
Name Name `json:"name"`
|
||||
|
||||
// Value is the value of the feature flag.
|
||||
Variant string `json:"variant"`
|
||||
}
|
||||
|
||||
func NewBooleanFeatureVariants() map[string]any {
|
||||
return map[string]any{
|
||||
KindBooleanVariantEnabled: true,
|
||||
KindBooleanVariantDisabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
func GetVariantValue[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
|
||||
}
|
||||
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-zA-Z]+$`)
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
134
pkg/types/featuretypes/registry.go
Normal file
134
pkg/types/featuretypes/registry.go
Normal file
@@ -0,0 +1,134 @@
|
||||
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(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:
|
||||
if _, ok := feature.Variants[feature.DefaultVariant].(bool); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q is not a boolean", feature.DefaultVariant)
|
||||
}
|
||||
for variant, value := range feature.Variants {
|
||||
if _, ok := value.(bool); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, variant %q is not a boolean", variant)
|
||||
}
|
||||
}
|
||||
case KindString:
|
||||
if _, ok := feature.Variants[feature.DefaultVariant].(string); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q is not a string", feature.DefaultVariant)
|
||||
}
|
||||
for variant, value := range feature.Variants {
|
||||
if _, ok := value.(string); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, variant %q is not a string", variant)
|
||||
}
|
||||
}
|
||||
case KindInt:
|
||||
if _, ok := feature.Variants[feature.DefaultVariant].(int); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q is not an int", feature.DefaultVariant)
|
||||
}
|
||||
for variant, value := range feature.Variants {
|
||||
if _, ok := value.(int); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, variant %q is not an int", variant)
|
||||
}
|
||||
}
|
||||
case KindFloat:
|
||||
if _, ok := feature.Variants[feature.DefaultVariant].(float64); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q is not a float", feature.DefaultVariant)
|
||||
}
|
||||
for variant, value := range feature.Variants {
|
||||
if _, ok := value.(float64); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, variant %q is not a float", variant)
|
||||
}
|
||||
}
|
||||
case KindObject:
|
||||
if _, ok := feature.Variants[feature.DefaultVariant].(map[string]any); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, default variant %q is not an object", feature.DefaultVariant)
|
||||
}
|
||||
for variant, value := range feature.Variants {
|
||||
if _, ok := value.(map[string]any); !ok {
|
||||
return nil, fmt.Errorf("cannot build registry, variant %q is not an object", variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registry.features[feature.Name] = feature
|
||||
}
|
||||
|
||||
return registry, nil
|
||||
}
|
||||
|
||||
func (registry *registry) MergeOrOverride(other Registry) Registry {
|
||||
for _, feature := range other.List() {
|
||||
if _, ok := registry.features[feature.Name]; ok {
|
||||
registry.features[feature.Name] = feature
|
||||
}
|
||||
|
||||
registry.features[feature.Name] = feature
|
||||
}
|
||||
|
||||
return registry
|
||||
}
|
||||
|
||||
func (registry *registry) Get(flag string) (*Feature, openfeature.ProviderResolutionDetail, error) {
|
||||
name, err := NewName(flag)
|
||||
if err != nil {
|
||||
return nil, openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewFlagNotFoundResolutionError(err.Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
}, err
|
||||
}
|
||||
|
||||
feature, ok := registry.features[name]
|
||||
if !ok {
|
||||
return nil, openfeature.ProviderResolutionDetail{
|
||||
ResolutionError: openfeature.NewFlagNotFoundResolutionError(errors.Newf(errors.TypeNotFound, ErrCodeFeatureNotFound, "feature %s not found in registry", name.String()).Error()),
|
||||
Reason: openfeature.ErrorReason,
|
||||
}, err
|
||||
}
|
||||
|
||||
return feature, openfeature.ProviderResolutionDetail{}, nil
|
||||
}
|
||||
|
||||
func (registry *registry) List() []*Feature {
|
||||
features := make([]*Feature, 0, len(registry.features))
|
||||
for _, feature := range registry.features {
|
||||
features = append(features, feature)
|
||||
}
|
||||
|
||||
return features
|
||||
}
|
||||
73
pkg/types/licensetypes/license.go
Normal file
73
pkg/types/licensetypes/license.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package licensetypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type GettableLicenseParams struct {
|
||||
Active *bool
|
||||
}
|
||||
|
||||
type GettableLicenses = []License
|
||||
|
||||
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
|
||||
FeatureValues() []*featuretypes.FeatureValue
|
||||
}
|
||||
|
||||
func NewGettableLicenseParams(req *http.Request) (GettableLicenseParams, error) {
|
||||
params := GettableLicenseParams{
|
||||
Active: nil,
|
||||
}
|
||||
|
||||
queryValues := req.URL.Query()
|
||||
|
||||
if active := queryValues.Get("active"); active != "" {
|
||||
activeBool, err := strconv.ParseBool(active)
|
||||
if err != nil {
|
||||
return GettableLicenseParams{}, err
|
||||
}
|
||||
|
||||
params.Active = &activeBool
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
// Set creates or updates a license.
|
||||
Set(context.Context, License) error
|
||||
|
||||
// Get returns the license for the given orgID
|
||||
Get(context.Context, valuer.UUID) ([]License, error)
|
||||
|
||||
// GetLatest returns the latest license for the given orgID
|
||||
GetLatest(context.Context, valuer.UUID) (License, error)
|
||||
|
||||
// ListOrgs returns the list of orgs
|
||||
ListOrgs(context.Context) ([]valuer.UUID, error)
|
||||
}
|
||||
42
pkg/types/licensetypes/noop.go
Normal file
42
pkg/types/licensetypes/noop.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package licensetypes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Noop struct{}
|
||||
|
||||
func NewNoop() License {
|
||||
return &Noop{}
|
||||
}
|
||||
|
||||
func (*Noop) ID() valuer.UUID {
|
||||
return valuer.UUID{}
|
||||
}
|
||||
|
||||
func (*Noop) OrgID() valuer.UUID {
|
||||
return valuer.UUID{}
|
||||
}
|
||||
|
||||
func (*Noop) Contents() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (*Noop) Key() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*Noop) CreatedAt() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (*Noop) UpdatedAt() time.Time {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (*Noop) FeatureValues() []*featuretypes.FeatureValue {
|
||||
return []*featuretypes.FeatureValue{}
|
||||
}
|
||||
5
pkg/types/metertypes/meter.go
Normal file
5
pkg/types/metertypes/meter.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package metertypes
|
||||
|
||||
type Meter struct{}
|
||||
|
||||
type Meters = []*Meter
|
||||
65
pkg/valuer/string.go
Normal file
65
pkg/valuer/string.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package valuer
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ Valuer = (*String)(nil)
|
||||
|
||||
type String struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func NewString(val string) String {
|
||||
return String{val: strings.ToLower(strings.TrimSpace(val))}
|
||||
}
|
||||
|
||||
func (enum String) IsZero() bool {
|
||||
return enum.val == ""
|
||||
}
|
||||
|
||||
func (enum String) StringValue() string {
|
||||
return enum.val
|
||||
}
|
||||
|
||||
func (enum String) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(enum.StringValue())
|
||||
}
|
||||
|
||||
func (enum *String) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
if err := json.Unmarshal(data, &str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*enum = NewString(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enum String) Value() (driver.Value, error) {
|
||||
return enum.StringValue(), nil
|
||||
}
|
||||
|
||||
func (enum *String) Scan(val interface{}) error {
|
||||
if enum == nil {
|
||||
return fmt.Errorf("string: (nil \"%s\")", reflect.TypeOf(enum).String())
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
// Return an empty string
|
||||
*enum = NewString("")
|
||||
return nil
|
||||
}
|
||||
|
||||
str, ok := val.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("string: (non-string \"%s\")", reflect.TypeOf(val).String())
|
||||
}
|
||||
|
||||
*enum = NewString(str)
|
||||
return nil
|
||||
}
|
||||
17
pkg/zeus/config.go
Normal file
17
pkg/zeus/config.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package zeus
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
var _ factory.Config = (*Config)(nil)
|
||||
|
||||
type Config struct {
|
||||
URL *url.URL `mapstructure:"url"`
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
43
pkg/zeus/noopzeus/provider.go
Normal file
43
pkg/zeus/noopzeus/provider.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package noopzeus
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metertypes"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type Provider struct{}
|
||||
|
||||
func NewProviderFactory() factory.ProviderFactory[zeus.Zeus, zeus.Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, providerSettings factory.ProviderSettings, config zeus.Config) (zeus.Zeus, error) {
|
||||
return New(ctx, providerSettings, config)
|
||||
})
|
||||
}
|
||||
|
||||
func New(_ context.Context, _ factory.ProviderSettings, _ zeus.Config) (zeus.Zeus, error) {
|
||||
return &Provider{}, nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetLicense(_ context.Context, _ string) (*licensetypes.License, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "this operation is not supported")
|
||||
}
|
||||
|
||||
func (provider *Provider) GetCheckoutURL(ctx context.Context, key string) (string, error) {
|
||||
return "", errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "this operation is not supported")
|
||||
}
|
||||
|
||||
func (provider *Provider) GetPortalURL(ctx context.Context, key string) (string, error) {
|
||||
return "", errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "this operation is not supported")
|
||||
}
|
||||
|
||||
func (provider *Provider) GetDeployment(ctx context.Context, key string) ([]byte, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "this operation is not supported")
|
||||
}
|
||||
|
||||
func (provider *Provider) PutMeters(ctx context.Context, key string, meters metertypes.Meters) error {
|
||||
return errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "this operation is not supported")
|
||||
}
|
||||
25
pkg/zeus/zeus.go
Normal file
25
pkg/zeus/zeus.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package zeus
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/metertypes"
|
||||
)
|
||||
|
||||
type Zeus interface {
|
||||
// Returns the license for the given key.
|
||||
GetLicense(context.Context, string) (licensetypes.License, error)
|
||||
|
||||
// Returns the checkout URL for the given license key.
|
||||
GetCheckoutURL(context.Context, string) (string, error)
|
||||
|
||||
// Returns the portal URL for the given license key.
|
||||
GetPortalURL(context.Context, string) (string, error)
|
||||
|
||||
// Returns the deployment for the given license key.
|
||||
GetDeployment(context.Context, string) ([]byte, error)
|
||||
|
||||
// Puts the usage for the given license key.
|
||||
PutMeters(context.Context, string, metertypes.Meters) error
|
||||
}
|
||||
Reference in New Issue
Block a user