From 0cf9003e3a22946b12d618359e09f56179cbbd25 Mon Sep 17 00:00:00 2001 From: Vibhu Pandey Date: Mon, 20 Jan 2025 17:45:33 +0530 Subject: [PATCH] feat(.): initialize all factories (#6844) ### Summary feat(.): initialize all factories #### Related Issues / PR's Removed all redundant commits of https://github.com/SigNoz/signoz/pull/6843 Closes https://github.com/SigNoz/signoz/pull/6782 --- conf/defaults.yaml | 32 ------- conf/example.yaml | 70 ++++++++++++++ ee/query-service/app/server.go | 9 +- ee/query-service/main.go | 22 ++--- go.mod | 5 +- pkg/cache/config.go | 14 +-- pkg/cache/memorycache/provider.go | 29 +++--- pkg/cache/memorycache/provider_test.go | 84 ++++++++++------- pkg/cache/rediscache/provider.go | 28 +++--- pkg/config/conf.go | 90 ++++++++++++++++++ pkg/config/conf_test.go | 38 ++++++++ pkg/config/config.go | 46 ++++----- pkg/config/config_test.go | 54 ----------- pkg/config/envprovider/provider.go | 71 ++++++++++++++ pkg/config/envprovider/provider_test.go | 90 ++++++++++++++++++ pkg/config/fileprovider/provider.go | 34 +++++++ pkg/config/fileprovider/provider_test.go | 68 ++++++++++++++ pkg/config/fileprovider/testdata/gotypes.yaml | 6 ++ pkg/config/fileprovider/testdata/strings.yaml | 8 ++ pkg/config/provider.go | 75 ++++++--------- pkg/config/resolver.go | 87 +++++++++++++++++ pkg/config/unmarshaler.go | 49 ---------- pkg/config/uri.go | 46 +++++++++ pkg/config/uri_test.go | 35 +++++++ pkg/confmap/config.go | 9 -- pkg/confmap/doc.go | 3 - .../provider/signozenvprovider/provider.go | 94 ------------------- .../signozenvprovider/provider_test.go | 40 -------- pkg/factory/provider.go | 18 +++- pkg/factory/provider_test.go | 4 +- pkg/factory/providertest/setting.go | 10 ++ pkg/http/server/config.go | 18 ---- pkg/http/server/server.go | 16 +--- pkg/query-service/app/server.go | 12 ++- pkg/query-service/main.go | 21 +++++ pkg/registry/registry.go | 9 +- pkg/registry/registry_test.go | 9 +- pkg/registry/service.go | 16 ---- pkg/registry/service_test.go | 49 ---------- pkg/signoz/config.go | 53 +++++++++++ pkg/signoz/provider.go | 54 +++++++++++ pkg/signoz/provider_test.go | 16 ++++ pkg/signoz/signoz.go | 55 +++++++---- .../000_add_data_migrations.go | 7 +- .../001_add_organization.go | 7 +- .../002_add_preferences.go | 7 +- .../003_add_dashboards.go | 31 +++--- .../004_add_saved_views.go | 7 +- .../005_add_agents.go | 7 +- .../006_add_pipelines.go | 7 +- .../007_add_integrations.go | 7 +- pkg/sqlmigration/config.go | 19 ++++ .../sqlmigration.go} | 19 +++- .../sqlmigrationtest}/000_noop.go | 7 +- pkg/sqlmigrator/migrator_test.go | 4 +- pkg/sqlmigrator/sqlmigrator.go | 14 --- pkg/web/config.go | 25 +++-- pkg/web/config_test.go | 45 +++++++++ pkg/web/noopweb/provider.go | 4 + pkg/web/routerweb/provider.go | 41 ++++---- pkg/web/routerweb/provider_test.go | 7 +- 61 files changed, 1201 insertions(+), 660 deletions(-) delete mode 100644 conf/defaults.yaml create mode 100644 conf/example.yaml create mode 100644 pkg/config/conf.go create mode 100644 pkg/config/conf_test.go delete mode 100644 pkg/config/config_test.go create mode 100644 pkg/config/envprovider/provider.go create mode 100644 pkg/config/envprovider/provider_test.go create mode 100644 pkg/config/fileprovider/provider.go create mode 100644 pkg/config/fileprovider/provider_test.go create mode 100644 pkg/config/fileprovider/testdata/gotypes.yaml create mode 100644 pkg/config/fileprovider/testdata/strings.yaml create mode 100644 pkg/config/resolver.go delete mode 100644 pkg/config/unmarshaler.go create mode 100644 pkg/config/uri.go create mode 100644 pkg/config/uri_test.go delete mode 100644 pkg/confmap/config.go delete mode 100644 pkg/confmap/doc.go delete mode 100644 pkg/confmap/provider/signozenvprovider/provider.go delete mode 100644 pkg/confmap/provider/signozenvprovider/provider_test.go create mode 100644 pkg/factory/providertest/setting.go delete mode 100644 pkg/registry/service.go delete mode 100644 pkg/registry/service_test.go create mode 100644 pkg/signoz/config.go create mode 100644 pkg/signoz/provider.go create mode 100644 pkg/signoz/provider_test.go rename pkg/{sqlmigrator/migration => sqlmigration}/000_add_data_migrations.go (81%) rename pkg/{sqlmigrator/migration => sqlmigration}/001_add_organization.go (93%) rename pkg/{sqlmigrator/migration => sqlmigration}/002_add_preferences.go (86%) rename pkg/{sqlmigrator/migration => sqlmigration}/003_add_dashboards.go (76%) rename pkg/{sqlmigrator/migration => sqlmigration}/004_add_saved_views.go (83%) rename pkg/{sqlmigrator/migration => sqlmigration}/005_add_agents.go (91%) rename pkg/{sqlmigrator/migration => sqlmigration}/006_add_pipelines.go (82%) rename pkg/{sqlmigrator/migration => sqlmigration}/007_add_integrations.go (85%) create mode 100644 pkg/sqlmigration/config.go rename pkg/{sqlmigrator/migration.go => sqlmigration/sqlmigration.go} (73%) rename pkg/{sqlmigrator => sqlmigration/sqlmigrationtest}/000_noop.go (69%) create mode 100644 pkg/web/config_test.go diff --git a/conf/defaults.yaml b/conf/defaults.yaml deleted file mode 100644 index 9239005fdfb1..000000000000 --- a/conf/defaults.yaml +++ /dev/null @@ -1,32 +0,0 @@ -##################### SigNoz Configuration Defaults ##################### -# -# Do not modify this file -# - -##################### Web ##################### -web: - # The prefix to serve web on - prefix: / - # The directory containing the static build files. - directory: /etc/signoz/web - -##################### Cache ##################### -cache: - # specifies the caching provider to use. - provider: memory - # memory: Uses in-memory caching. - memory: - # Time-to-live for cache entries in memory. Specify the duration in ns - ttl: 60000000000 - # The interval at which the cache will be cleaned up - cleanupInterval: - # redis: Uses Redis as the caching backend. - redis: - # The hostname or IP address of the Redis server. - host: localhost - # The port on which the Redis server is running. Default is usually 6379. - port: 6379 - # The password for authenticating with the Redis server, if required. - password: - # The Redis database number to use - db: 0 \ No newline at end of file diff --git a/conf/example.yaml b/conf/example.yaml new file mode 100644 index 000000000000..47b08568874f --- /dev/null +++ b/conf/example.yaml @@ -0,0 +1,70 @@ +##################### SigNoz Configuration Example ##################### +# +# Do not modify this file +# + +##################### Instrumentation ##################### +instrumentation: + logs: + level: info + enabled: false + processors: + batch: + exporter: + otlp: + endpoint: localhost:4317 + traces: + enabled: false + processors: + batch: + exporter: + otlp: + endpoint: localhost:4317 + metrics: + enabled: true + readers: + pull: + exporter: + prometheus: + host: "0.0.0.0" + port: 9090 + +##################### Web ##################### +web: + # Whether to enable the web frontend + enabled: true + # The prefix to serve web on + prefix: / + # The directory containing the static build files. + directory: /etc/signoz/web + +##################### Cache ##################### +cache: + # specifies the caching provider to use. + provider: memory + # memory: Uses in-memory caching. + memory: + # Time-to-live for cache entries in memory. Specify the duration in ns + ttl: 60000000000 + # The interval at which the cache will be cleaned up + cleanupInterval: 1m + # redis: Uses Redis as the caching backend. + redis: + # The hostname or IP address of the Redis server. + host: localhost + # The port on which the Redis server is running. Default is usually 6379. + port: 6379 + # The password for authenticating with the Redis server, if required. + password: + # The Redis database number to use + db: 0 + +##################### SQLStore ##################### +sqlstore: + # specifies the SQLStore provider to use. + provider: sqlite + # The maximum number of open connections to the database. + max_open_conns: 100 + sqlite: + # The path to the SQLite database file. + path: /var/lib/signoz/signoz.db diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index 93f2b9056282..c4df57db89ba 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -81,7 +81,6 @@ type ServerOptions struct { GatewayUrl string UseLogsNewSchema bool UseTraceNewSchema bool - SkipWebFrontend bool } // Server runs HTTP api service @@ -388,11 +387,9 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h handler = handlers.CompressHandler(handler) - if !s.serverOptions.SkipWebFrontend { - err := web.AddToRouter(r) - if err != nil { - return nil, err - } + err := web.AddToRouter(r) + if err != nil { + return nil, err } return &http.Server{ diff --git a/ee/query-service/main.go b/ee/query-service/main.go index f9713c22bdcd..b7f23f43b3e9 100644 --- a/ee/query-service/main.go +++ b/ee/query-service/main.go @@ -10,12 +10,12 @@ import ( "syscall" "time" - "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "go.signoz.io/signoz/ee/query-service/app" - signozconfig "go.signoz.io/signoz/pkg/config" - "go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider" + "go.signoz.io/signoz/pkg/config" + "go.signoz.io/signoz/pkg/config/envprovider" + "go.signoz.io/signoz/pkg/config/fileprovider" "go.signoz.io/signoz/pkg/query-service/auth" baseconst "go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/migrate" @@ -108,7 +108,6 @@ func main() { var dialTimeout time.Duration var gatewayUrl string var useLicensesV3 bool - var skipWebFrontend bool flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs") flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces") @@ -126,7 +125,6 @@ func main() { flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')") flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)") flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses") - flag.BoolVar(&skipWebFrontend, "skip-web-frontend", false, "skip web frontend") flag.Parse() loggerMgr := initZapLog(enableQueryServiceLogOTLPExport) @@ -136,19 +134,18 @@ func main() { version.PrintVersion() - config, err := signozconfig.New(context.Background(), signozconfig.ProviderSettings{ - ResolverSettings: confmap.ResolverSettings{ - URIs: []string{"signozenv:"}, - ProviderFactories: []confmap.ProviderFactory{ - signozenvprovider.NewFactory(), - }, + config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{ + Uris: []string{"env:"}, + ProviderFactories: []config.ProviderFactory{ + envprovider.NewFactory(), + fileprovider.NewFactory(), }, }) if err != nil { zap.L().Fatal("Failed to create config", zap.Error(err)) } - signoz, err := signoz.New(config, skipWebFrontend) + signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig()) if err != nil { zap.L().Fatal("Failed to create signoz struct", zap.Error(err)) } @@ -171,7 +168,6 @@ func main() { GatewayUrl: gatewayUrl, UseLogsNewSchema: useLogsNewSchema, UseTraceNewSchema: useTraceNewSchema, - SkipWebFrontend: skipWebFrontend, } // Read the jwt secret key diff --git a/go.mod b/go.mod index fd4ae47279ed..0ab61746b43e 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-kit/log v0.2.1 github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redismock/v8 v8.11.5 + github.com/go-viper/mapstructure/v2 v2.1.0 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 github.com/gorilla/handlers v1.5.1 @@ -29,6 +30,7 @@ require ( github.com/jmoiron/sqlx v1.3.4 github.com/json-iterator/go v1.1.12 github.com/knadh/koanf v1.5.0 + github.com/knadh/koanf/v2 v2.1.1 github.com/mailru/easyjson v0.7.7 github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/oklog/oklog v0.3.2 @@ -101,6 +103,7 @@ require ( github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect @@ -108,7 +111,6 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect @@ -129,7 +131,6 @@ require ( github.com/jpillora/backoff v1.0.0 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/klauspost/compress v1.17.10 // indirect - github.com/knadh/koanf/v2 v2.1.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-syslog/v4 v4.2.0 // indirect github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect diff --git a/pkg/cache/config.go b/pkg/cache/config.go index 213fcaba0e6c..ad80b6275a81 100644 --- a/pkg/cache/config.go +++ b/pkg/cache/config.go @@ -4,12 +4,9 @@ import ( "time" go_cache "github.com/patrickmn/go-cache" - "go.signoz.io/signoz/pkg/confmap" + "go.signoz.io/signoz/pkg/factory" ) -// Config satisfies the confmap.Config interface -var _ confmap.Config = (*Config)(nil) - type Memory struct { TTL time.Duration `mapstructure:"ttl"` CleanupInterval time.Duration `mapstructure:"cleanupInterval"` @@ -28,7 +25,11 @@ type Config struct { Redis Redis `mapstructure:"redis"` } -func (c *Config) NewWithDefaults() confmap.Config { +func NewConfigFactory() factory.ConfigFactory { + return factory.NewConfigFactory(factory.MustNewName("cache"), newConfig) +} + +func newConfig() factory.Config { return &Config{ Provider: "memory", Memory: Memory{ @@ -42,8 +43,9 @@ func (c *Config) NewWithDefaults() confmap.Config { DB: 0, }, } + } -func (c *Config) Validate() error { +func (c Config) Validate() error { return nil } diff --git a/pkg/cache/memorycache/provider.go b/pkg/cache/memorycache/provider.go index dc02bfc45f4b..8b48d6fd9274 100644 --- a/pkg/cache/memorycache/provider.go +++ b/pkg/cache/memorycache/provider.go @@ -7,15 +7,20 @@ import ( "time" go_cache "github.com/patrickmn/go-cache" - _cache "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/factory" ) type provider struct { cc *go_cache.Cache } -func New(opts *_cache.Memory) *provider { - return &provider{cc: go_cache.New(opts.TTL, opts.CleanupInterval)} +func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] { + return factory.NewProviderFactory(factory.MustNewName("memory"), New) +} + +func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) { + return &provider{cc: go_cache.New(config.Memory.TTL, config.Memory.CleanupInterval)}, nil } // Connect does nothing @@ -24,11 +29,11 @@ func (c *provider) Connect(_ context.Context) error { } // Store stores the data in the cache -func (c *provider) Store(_ context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error { +func (c *provider) Store(_ context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error { // check if the data being passed is a pointer and is not nil rv := reflect.ValueOf(data) if rv.Kind() != reflect.Pointer || rv.IsNil() { - return _cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory") + return cache.WrapCacheableEntityErrors(reflect.TypeOf(data), "inmemory") } c.cc.Set(cacheKey, data, ttl) @@ -36,32 +41,32 @@ func (c *provider) Store(_ context.Context, cacheKey string, data _cache.Cacheab } // Retrieve retrieves the data from the cache -func (c *provider) Retrieve(_ context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) { +func (c *provider) Retrieve(_ context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) { // check if the destination being passed is a pointer and is not nil dstv := reflect.ValueOf(dest) if dstv.Kind() != reflect.Pointer || dstv.IsNil() { - return _cache.RetrieveStatusError, _cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory") + return cache.RetrieveStatusError, cache.WrapCacheableEntityErrors(reflect.TypeOf(dest), "inmemory") } // check if the destination value is settable if !dstv.Elem().CanSet() { - return _cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem()) + return cache.RetrieveStatusError, fmt.Errorf("destination value is not settable, %s", dstv.Elem()) } data, found := c.cc.Get(cacheKey) if !found { - return _cache.RetrieveStatusKeyMiss, nil + return cache.RetrieveStatusKeyMiss, nil } // check the type compatbility between the src and dest srcv := reflect.ValueOf(data) if !srcv.Type().AssignableTo(dstv.Type()) { - return _cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type") + return cache.RetrieveStatusError, fmt.Errorf("src type is not assignable to dst type") } // set the value to from src to dest dstv.Elem().Set(srcv.Elem()) - return _cache.RetrieveStatusHit, nil + return cache.RetrieveStatusHit, nil } // SetTTL sets the TTL for the cache entry @@ -91,6 +96,6 @@ func (c *provider) Close(_ context.Context) error { } // Configuration returns the cache configuration -func (c *provider) Configuration() *_cache.Memory { +func (c *provider) Configuration() *cache.Memory { return nil } diff --git a/pkg/cache/memorycache/provider_test.go b/pkg/cache/memorycache/provider_test.go index c3767760ed0e..e17b36eb7b03 100644 --- a/pkg/cache/memorycache/provider_test.go +++ b/pkg/cache/memorycache/provider_test.go @@ -7,18 +7,21 @@ import ( "time" "github.com/stretchr/testify/assert" - _cache "go.signoz.io/signoz/pkg/cache" + "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/factory/providertest" ) // TestNew tests the New function func TestNew(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) assert.NotNil(t, c) - assert.NotNil(t, c.cc) + assert.NotNil(t, c.(*provider).cc) assert.NoError(t, c.Connect(context.Background())) } @@ -53,32 +56,35 @@ func (dce DCacheableEntity) UnmarshalBinary(data []byte) error { // TestStore tests the Store function // this should fail because of nil pointer error func TestStoreWithNilPointer(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) var storeCacheableEntity *CacheableEntity assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second)) } // this should fail because of no pointer error func TestStoreWithStruct(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) var storeCacheableEntity CacheableEntity assert.Error(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second)) } func TestStoreWithNonNilPointer(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -89,11 +95,12 @@ func TestStoreWithNonNilPointer(t *testing.T) { // TestRetrieve tests the Retrieve function func TestRetrieveWithNilPointer(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -105,15 +112,16 @@ func TestRetrieveWithNilPointer(t *testing.T) { retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.Error(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusError) } func TestRetrieveWitNonPointer(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -125,15 +133,16 @@ func TestRetrieveWitNonPointer(t *testing.T) { retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.Error(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusError) } func TestRetrieveWithDifferentTypes(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -144,15 +153,16 @@ func TestRetrieveWithDifferentTypes(t *testing.T) { retrieveCacheableEntity := new(DCacheableEntity) retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.Error(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusError) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusError) } func TestRetrieveWithSameTypes(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -163,13 +173,14 @@ func TestRetrieveWithSameTypes(t *testing.T) { retrieveCacheableEntity := new(CacheableEntity) retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit) assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity) } // TestSetTTL tests the SetTTL function func TestSetTTL(t *testing.T) { - c := New(&_cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: cache.Memory{TTL: 10 * time.Second, CleanupInterval: 1 * time.Second}}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -180,7 +191,7 @@ func TestSetTTL(t *testing.T) { time.Sleep(3 * time.Second) retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss) assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity) assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 2*time.Second)) @@ -188,17 +199,18 @@ func TestSetTTL(t *testing.T) { time.Sleep(3 * time.Second) retrieveStatus, err = c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit) assert.Equal(t, retrieveCacheableEntity, storeCacheableEntity) } // TestRemove tests the Remove function func TestRemove(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -210,17 +222,18 @@ func TestRemove(t *testing.T) { retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss) assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity) } // TestBulkRemove tests the BulkRemove function func TestBulkRemove(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -233,22 +246,23 @@ func TestBulkRemove(t *testing.T) { retrieveStatus, err := c.Retrieve(context.Background(), "key1", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss) assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity) retrieveStatus, err = c.Retrieve(context.Background(), "key2", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusKeyMiss) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusKeyMiss) assert.Equal(t, new(CacheableEntity), retrieveCacheableEntity) } // TestCache tests the cache func TestCache(t *testing.T) { - opts := &_cache.Memory{ + opts := cache.Memory{ TTL: 10 * time.Second, CleanupInterval: 10 * time.Second, } - c := New(opts) + c, err := New(context.Background(), providertest.NewSettings(), cache.Config{Provider: "memory", Memory: opts}) + require.NoError(t, err) storeCacheableEntity := &CacheableEntity{ Key: "some-random-key", Value: 1, @@ -258,7 +272,7 @@ func TestCache(t *testing.T) { assert.NoError(t, c.Store(context.Background(), "key", storeCacheableEntity, 10*time.Second)) retrieveStatus, err := c.Retrieve(context.Background(), "key", retrieveCacheableEntity, false) assert.NoError(t, err) - assert.Equal(t, retrieveStatus, _cache.RetrieveStatusHit) + assert.Equal(t, retrieveStatus, cache.RetrieveStatusHit) assert.Equal(t, storeCacheableEntity, retrieveCacheableEntity) c.Remove(context.Background(), "key") } diff --git a/pkg/cache/rediscache/provider.go b/pkg/cache/rediscache/provider.go index 13ced5422708..171f08cf646a 100644 --- a/pkg/cache/rediscache/provider.go +++ b/pkg/cache/rediscache/provider.go @@ -7,17 +7,22 @@ import ( "time" "github.com/go-redis/redis/v8" - _cache "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/factory" "go.uber.org/zap" ) type provider struct { client *redis.Client - opts *_cache.Redis + opts cache.Redis } -func New(opts *_cache.Redis) *provider { - return &provider{opts: opts} +func NewFactory() factory.ProviderFactory[cache.Cache, cache.Config] { + return factory.NewProviderFactory(factory.MustNewName("redis"), New) +} + +func New(ctx context.Context, settings factory.ProviderSettings, config cache.Config) (cache.Cache, error) { + return &provider{opts: config.Redis}, nil } // WithClient creates a new cache with the given client @@ -36,20 +41,20 @@ func (c *provider) Connect(_ context.Context) error { } // Store stores the data in the cache -func (c *provider) Store(ctx context.Context, cacheKey string, data _cache.CacheableEntity, ttl time.Duration) error { +func (c *provider) Store(ctx context.Context, cacheKey string, data cache.CacheableEntity, ttl time.Duration) error { return c.client.Set(ctx, cacheKey, data, ttl).Err() } // Retrieve retrieves the data from the cache -func (c *provider) Retrieve(ctx context.Context, cacheKey string, dest _cache.CacheableEntity, allowExpired bool) (_cache.RetrieveStatus, error) { +func (c *provider) Retrieve(ctx context.Context, cacheKey string, dest cache.CacheableEntity, allowExpired bool) (cache.RetrieveStatus, error) { err := c.client.Get(ctx, cacheKey).Scan(dest) if err != nil { if errors.Is(err, redis.Nil) { - return _cache.RetrieveStatusKeyMiss, nil + return cache.RetrieveStatusKeyMiss, nil } - return _cache.RetrieveStatusError, err + return cache.RetrieveStatusError, err } - return _cache.RetrieveStatusHit, nil + return cache.RetrieveStatusHit, nil } // SetTTL sets the TTL for the cache entry @@ -87,11 +92,6 @@ func (c *provider) GetClient() *redis.Client { return c.client } -// GetOptions returns the options -func (c *provider) GetOptions() *_cache.Redis { - return c.opts -} - // GetTTL returns the TTL for the cache entry func (c *provider) GetTTL(ctx context.Context, cacheKey string) time.Duration { ttl, err := c.client.TTL(ctx, cacheKey).Result() diff --git a/pkg/config/conf.go b/pkg/config/conf.go new file mode 100644 index 000000000000..5b34dddf161b --- /dev/null +++ b/pkg/config/conf.go @@ -0,0 +1,90 @@ +package config + +import ( + "github.com/go-viper/mapstructure/v2" + "github.com/knadh/koanf/providers/confmap" + "github.com/knadh/koanf/v2" +) + +const ( + KoanfDelimiter string = "::" +) + +// Conf is a wrapper around the koanf library. +type Conf struct { + *koanf.Koanf +} + +// NewConf creates a new Conf instance. +func NewConf() *Conf { + return &Conf{koanf.New(KoanfDelimiter)} +} + +// NewConfFromMap creates a new Conf instance from a map. +func NewConfFromMap(m map[string]any) (*Conf, error) { + conf := NewConf() + if err := conf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil { + return nil, err + } + + return conf, nil +} + +// MustNewConfFromMap creates a new Conf instance from a map. +// It panics if the conf cannot be created. +func MustNewConfFromMap(m map[string]any) *Conf { + conf, err := NewConfFromMap(m) + if err != nil { + panic(err) + } + + return conf +} + +// Merge merges the current configuration with the input configuration. +func (conf *Conf) Merge(input *Conf) error { + return conf.Koanf.Merge(input.Koanf) +} + +// Merge merges the current configuration with the input configuration. +func (conf *Conf) MergeAt(input *Conf, path string) error { + return conf.Koanf.MergeAt(input.Koanf, path) +} + +// Unmarshal unmarshals the configuration at the given path into the input. +// It uses a WeaklyTypedInput to allow for more flexible unmarshalling. +func (conf *Conf) Unmarshal(path string, input any) error { + dc := &mapstructure.DecoderConfig{ + TagName: "mapstructure", + WeaklyTypedInput: true, + DecodeHook: mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToSliceHookFunc(","), + mapstructure.StringToTimeDurationHookFunc(), + mapstructure.TextUnmarshallerHookFunc(), + ), + Result: input, + } + + return conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: "mapstructure", DecoderConfig: dc}) +} + +// Set sets the configuration at the given key. +// It decodes the input into a map as per mapstructure.Decode and then merges it into the configuration. +func (conf *Conf) Set(key string, input any) error { + m := map[string]any{} + err := mapstructure.Decode(input, &m) + if err != nil { + return err + } + + newConf := NewConf() + if err := newConf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil { + return err + } + + if err := conf.Koanf.MergeAt(newConf.Koanf, key); err != nil { + return err + } + + return nil +} diff --git a/pkg/config/conf_test.go b/pkg/config/conf_test.go new file mode 100644 index 000000000000..e939d98b60cb --- /dev/null +++ b/pkg/config/conf_test.go @@ -0,0 +1,38 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConfMerge(t *testing.T) { + testCases := []struct { + name string + conf *Conf + input *Conf + expected *Conf + pass bool + }{ + {name: "Empty", conf: NewConf(), input: NewConf(), expected: NewConf(), pass: true}, + {name: "Merge", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"c": "d"}), expected: MustNewConfFromMap(map[string]any{"a": "b", "c": "d"}), pass: true}, + {name: "NestedMerge", conf: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2"}}), input: MustNewConfFromMap(map[string]any{"a": map[string]any{"d": "v1", "e": "v2"}}), expected: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2", "d": "v1", "e": "v2"}}), pass: true}, + {name: "Override", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"a": "c"}), expected: MustNewConfFromMap(map[string]any{"a": "c"}), pass: true}, + } + + t.Parallel() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.conf.Merge(tc.input) + if !tc.pass { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expected.Raw(), tc.conf.Raw()) + assert.Equal(t, tc.expected.Raw(), tc.conf.Raw()) + }) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 768792258bd9..1261816ba386 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,32 +3,34 @@ package config import ( "context" - "go.signoz.io/signoz/pkg/cache" - signozconfmap "go.signoz.io/signoz/pkg/confmap" - "go.signoz.io/signoz/pkg/instrumentation" - "go.signoz.io/signoz/pkg/web" + "go.signoz.io/signoz/pkg/factory" ) -// This map contains the default values of all config structs -var ( - defaults = map[string]signozconfmap.Config{ - "web": &web.Config{}, - "cache": &cache.Config{}, - } -) - -// Config defines the entire configuration of signoz. -type Config struct { - Instrumentation instrumentation.Config `mapstructure:"instrumentation"` - Web web.Config `mapstructure:"web"` - Cache cache.Config `mapstructure:"cache"` -} - -func New(ctx context.Context, settings ProviderSettings) (*Config, error) { - provider, err := NewProvider(settings) +func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) { + // Get the config from the resolver + resolver, err := NewResolver(resolverConfig) if err != nil { return nil, err } - return provider.Get(ctx) + resolvedConf, err := resolver.Do(ctx) + if err != nil { + return nil, err + } + + conf := NewConf() + // Set the default configs + for _, factory := range configFactories { + c := factory.New() + if err := conf.Set(factory.Name().String(), c); err != nil { + return nil, err + } + } + + err = conf.Merge(resolvedConf) + if err != nil { + return nil, err + } + + return conf, nil } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go deleted file mode 100644 index ac2ce3e762c9..000000000000 --- a/pkg/config/config_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package config - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/confmap" - "go.signoz.io/signoz/pkg/cache" - "go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider" - "go.signoz.io/signoz/pkg/web" -) - -func TestNewWithSignozEnvProvider(t *testing.T) { - - t.Setenv("SIGNOZ__WEB__PREFIX", "/web") - t.Setenv("SIGNOZ__WEB__DIRECTORY", "/build") - t.Setenv("SIGNOZ__CACHE__PROVIDER", "redis") - t.Setenv("SIGNOZ__CACHE__REDIS__HOST", "127.0.0.1") - - config, err := New(context.Background(), ProviderSettings{ - ResolverSettings: confmap.ResolverSettings{ - URIs: []string{"signozenv:"}, - ProviderFactories: []confmap.ProviderFactory{ - signozenvprovider.NewFactory(), - }, - }, - }) - require.NoError(t, err) - - expected := &Config{ - Web: web.Config{ - Prefix: "/web", - Directory: "/build", - }, - Cache: cache.Config{ - Provider: "redis", - Memory: cache.Memory{ - TTL: time.Duration(-1), - CleanupInterval: 1 * time.Minute, - }, - Redis: cache.Redis{ - Host: "127.0.0.1", - Port: 6379, - Password: "", - DB: 0, - }, - }, - } - - assert.Equal(t, expected, config) -} diff --git a/pkg/config/envprovider/provider.go b/pkg/config/envprovider/provider.go new file mode 100644 index 000000000000..3f6a591a505d --- /dev/null +++ b/pkg/config/envprovider/provider.go @@ -0,0 +1,71 @@ +package envprovider + +import ( + "context" + "strings" + + koanfenv "github.com/knadh/koanf/providers/env" + "go.signoz.io/signoz/pkg/config" +) + +const ( + prefix string = "SIGNOZ_" + scheme string = "env" +) + +type provider struct{} + +func NewFactory() config.ProviderFactory { + return config.NewProviderFactory(New) +} + +func New(config config.ProviderConfig) config.Provider { + return &provider{} +} + +func (provider *provider) Scheme() string { + return scheme +} + +func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) { + conf := config.NewConf() + err := conf.Load( + koanfenv.Provider( + prefix, + // Do not set this to `_`. The correct delimiter is being set by the custom callback provided below. + // Since this had to be passed, using `config.KoanfDelimiter` eliminates any possible side effect. + config.KoanfDelimiter, + func(s string) string { + s = strings.ToLower(strings.TrimPrefix(s, prefix)) + return provider.cb(s, config.KoanfDelimiter) + }, + ), + nil, + ) + + return conf, err +} + +func (provider *provider) cb(s string, delim string) string { + delims := []rune(delim) + runes := []rune(s) + result := make([]rune, 0, len(runes)) + + for i := 0; i < len(runes); i++ { + // Check for double underscore pattern + if i < len(runes)-1 && runes[i] == '_' && runes[i+1] == '_' { + result = append(result, '_') + i++ // Skip next underscore + continue + } + + if runes[i] == '_' { + result = append(result, delims...) + continue + } + + result = append(result, runes[i]) + } + + return string(result) +} diff --git a/pkg/config/envprovider/provider_test.go b/pkg/config/envprovider/provider_test.go new file mode 100644 index 000000000000..43a095e7dcfc --- /dev/null +++ b/pkg/config/envprovider/provider_test.go @@ -0,0 +1,90 @@ +package envprovider + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/config" +) + +func TestGetWithStrings(t *testing.T) { + t.Setenv("SIGNOZ_K1_K2", "string") + t.Setenv("SIGNOZ_K3__K4", "string") + t.Setenv("SIGNOZ_K5__K6_K7__K8", "string") + t.Setenv("SIGNOZ_K9___K10", "string") + t.Setenv("SIGNOZ_K11____K12", "string") + expected := map[string]any{ + "k1::k2": "string", + "k3_k4": "string", + "k5_k6::k7_k8": "string", + "k9_::k10": "string", + "k11__k12": "string", + } + + provider := New(config.ProviderConfig{}) + actual, err := provider.Get(context.Background(), config.MustNewUri("env:")) + require.NoError(t, err) + + assert.Equal(t, expected, actual.All()) +} + +func TestGetWithNoPrefix(t *testing.T) { + t.Setenv("K1_K2", "string") + t.Setenv("K3_K4", "string") + expected := map[string]any{} + + provider := New(config.ProviderConfig{}) + actual, err := provider.Get(context.Background(), config.MustNewUri("env:")) + require.NoError(t, err) + + assert.Equal(t, expected, actual.All()) +} + +func TestGetWithGoTypes(t *testing.T) { + t.Setenv("SIGNOZ_BOOL", "true") + t.Setenv("SIGNOZ_STRING", "string") + t.Setenv("SIGNOZ_INT", "1") + t.Setenv("SIGNOZ_SLICE", "[1,2]") + expected := map[string]any{ + "bool": "true", + "int": "1", + "slice": "[1,2]", + "string": "string", + } + + provider := New(config.ProviderConfig{}) + actual, err := provider.Get(context.Background(), config.MustNewUri("env:")) + require.NoError(t, err) + + assert.Equal(t, expected, actual.All()) +} + +func TestGetWithGoTypesWithUnmarshal(t *testing.T) { + t.Setenv("SIGNOZ_BOOL", "true") + t.Setenv("SIGNOZ_STRING", "string") + t.Setenv("SIGNOZ_INT", "1") + + type test struct { + Bool bool `mapstructure:"bool"` + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + } + + expected := test{ + Bool: true, + String: "string", + Int: 1, + } + + provider := New(config.ProviderConfig{}) + conf, err := provider.Get(context.Background(), config.MustNewUri("env:")) + require.NoError(t, err) + + actual := test{} + err = conf.Unmarshal("", &actual) + require.NoError(t, err) + + assert.Equal(t, expected, actual) +} diff --git a/pkg/config/fileprovider/provider.go b/pkg/config/fileprovider/provider.go new file mode 100644 index 000000000000..fb184f9c5285 --- /dev/null +++ b/pkg/config/fileprovider/provider.go @@ -0,0 +1,34 @@ +package fileprovider + +import ( + "context" + + koanfyaml "github.com/knadh/koanf/parsers/yaml" + koanffile "github.com/knadh/koanf/providers/file" + "go.signoz.io/signoz/pkg/config" +) + +const ( + scheme string = "file" +) + +type provider struct{} + +func NewFactory() config.ProviderFactory { + return config.NewProviderFactory(New) +} + +func New(config config.ProviderConfig) config.Provider { + return &provider{} +} + +func (provider *provider) Scheme() string { + return scheme +} + +func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) { + conf := config.NewConf() + err := conf.Load(koanffile.Provider(uri.Value()), koanfyaml.Parser()) + + return conf, err +} diff --git a/pkg/config/fileprovider/provider_test.go b/pkg/config/fileprovider/provider_test.go new file mode 100644 index 000000000000..647672c8eac6 --- /dev/null +++ b/pkg/config/fileprovider/provider_test.go @@ -0,0 +1,68 @@ +package fileprovider + +import ( + "context" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/config" +) + +func TestGetWithStrings(t *testing.T) { + expected := map[string]any{ + "k1::k2": "string", + "k3_k4": "string", + "k5_k6::k7_k8": "string", + "k9_::k10": "string", + "k11__k12": "string", + } + + provider := New(config.ProviderConfig{}) + actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "strings.yaml"))) + require.NoError(t, err) + + assert.Equal(t, expected, actual.All()) +} + +func TestGetWithGoTypes(t *testing.T) { + expected := map[string]any{ + "bool": true, + "int": 1, + "slice": []any{1, 2}, + "string": "string", + } + + provider := New(config.ProviderConfig{}) + actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml"))) + require.NoError(t, err) + + assert.Equal(t, expected, actual.All()) +} + +func TestGetWithGoTypesWithUnmarshal(t *testing.T) { + type test struct { + Bool bool `mapstructure:"bool"` + String string `mapstructure:"string"` + Int int `mapstructure:"int"` + Slice []any `mapstructure:"slice"` + } + + expected := test{ + Bool: true, + String: "string", + Int: 1, + Slice: []any{1, 2}, + } + + provider := New(config.ProviderConfig{}) + conf, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml"))) + require.NoError(t, err) + + actual := test{} + err = conf.Unmarshal("", &actual) + require.NoError(t, err) + + assert.Equal(t, expected, actual) +} diff --git a/pkg/config/fileprovider/testdata/gotypes.yaml b/pkg/config/fileprovider/testdata/gotypes.yaml new file mode 100644 index 000000000000..99d5a189f83f --- /dev/null +++ b/pkg/config/fileprovider/testdata/gotypes.yaml @@ -0,0 +1,6 @@ +bool: true +string: string +int: 1 +slice: + - 1 + - 2 \ No newline at end of file diff --git a/pkg/config/fileprovider/testdata/strings.yaml b/pkg/config/fileprovider/testdata/strings.yaml new file mode 100644 index 000000000000..e44fb14e2fa8 --- /dev/null +++ b/pkg/config/fileprovider/testdata/strings.yaml @@ -0,0 +1,8 @@ +k1: + k2: string +k3_k4: string +k5_k6: + k7_k8: string +k9_: + k10: string +k11__k12: string diff --git a/pkg/config/provider.go b/pkg/config/provider.go index c0164d1d0352..d49f78690f99 100644 --- a/pkg/config/provider.go +++ b/pkg/config/provider.go @@ -2,51 +2,38 @@ package config import ( "context" - "fmt" - - "go.opentelemetry.io/collector/confmap" ) -// Provides the configuration for signoz. +// NewProviderFunc is a function that creates a new provider. +type NewProviderFunc = func(ProviderConfig) Provider + +// ProviderFactory is a factory that creates a new provider. +type ProviderFactory interface { + New(ProviderConfig) Provider +} + +// NewProviderFactory creates a new provider factory. +func NewProviderFactory(f NewProviderFunc) ProviderFactory { + return &providerFactory{f: f} +} + +// providerFactory is a factory that implements the ProviderFactory interface. +type providerFactory struct { + f NewProviderFunc +} + +// New creates a new provider. +func (factory *providerFactory) New(config ProviderConfig) Provider { + return factory.f(config) +} + +// ProviderConfig is the configuration for a provider. +type ProviderConfig struct{} + +// Provider is an interface that represents a configuration provider. type Provider interface { - // Get returns the configuration, or error otherwise. - Get(ctx context.Context) (*Config, error) -} - -type provider struct { - resolver *confmap.Resolver -} - -// ProviderSettings are the settings to configure the behavior of the Provider. -type ProviderSettings struct { - // ResolverSettings are the settings to configure the behavior of the confmap.Resolver. - ResolverSettings confmap.ResolverSettings -} - -// NewProvider returns a new Provider that provides the entire configuration. -// See https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go for -// more details -func NewProvider(settings ProviderSettings) (Provider, error) { - resolver, err := confmap.NewResolver(settings.ResolverSettings) - if err != nil { - return nil, err - } - - return &provider{ - resolver: resolver, - }, nil -} - -func (provider *provider) Get(ctx context.Context) (*Config, error) { - conf, err := provider.resolver.Resolve(ctx) - if err != nil { - return nil, fmt.Errorf("cannot resolve configuration: %w", err) - } - - config, err := unmarshal(conf) - if err != nil { - return nil, fmt.Errorf("cannot unmarshal configuration: %w", err) - } - - return config, nil + // Get returns the configuration for the given URI. + Get(context.Context, Uri) (*Conf, error) + // Scheme returns the scheme of the provider. + Scheme() string } diff --git a/pkg/config/resolver.go b/pkg/config/resolver.go new file mode 100644 index 000000000000..98ef88f75399 --- /dev/null +++ b/pkg/config/resolver.go @@ -0,0 +1,87 @@ +package config + +import ( + "context" + "errors" + "fmt" +) + +type ResolverConfig struct { + // Each string or `uri` must follow ":" format. This format is compatible with the URI definition + // defined at https://datatracker.ietf.org/doc/html/rfc3986". + // It is required to have at least one uri. + Uris []string + + // ProviderFactories is a slice of Provider factories. + // It is required to have at least one factory. + ProviderFactories []ProviderFactory +} + +type Resolver struct { + uris []Uri + providers map[string]Provider +} + +func NewResolver(config ResolverConfig) (*Resolver, error) { + if len(config.Uris) == 0 { + return nil, errors.New("cannot build resolver, no uris have been provided") + } + + if len(config.ProviderFactories) == 0 { + return nil, errors.New("cannot build resolver, no providers have been provided") + } + + uris := make([]Uri, len(config.Uris)) + for i, inputUri := range config.Uris { + uri, err := NewUri(inputUri) + if err != nil { + return nil, err + } + + uris[i] = uri + } + + providers := make(map[string]Provider, len(config.ProviderFactories)) + for _, factory := range config.ProviderFactories { + provider := factory.New(ProviderConfig{}) + + scheme := provider.Scheme() + // Check that the scheme is unique. + if _, ok := providers[scheme]; ok { + return nil, fmt.Errorf("cannot build resolver, duplicate scheme %q found", scheme) + } + + providers[provider.Scheme()] = provider + } + + return &Resolver{ + uris: uris, + providers: providers, + }, nil +} + +func (resolver *Resolver) Do(ctx context.Context) (*Conf, error) { + conf := NewConf() + + for _, uri := range resolver.uris { + currentConf, err := resolver.get(ctx, uri) + if err != nil { + return nil, err + } + + if err = conf.Merge(currentConf); err != nil { + return nil, fmt.Errorf("cannot merge config: %w", err) + } + } + + return conf, nil +} + +func (resolver *Resolver) get(ctx context.Context, uri Uri) (*Conf, error) { + provider, ok := resolver.providers[uri.scheme] + if !ok { + return nil, fmt.Errorf("cannot find provider with schema %q", uri.scheme) + } + + return provider.Get(ctx, uri) +} diff --git a/pkg/config/unmarshaler.go b/pkg/config/unmarshaler.go deleted file mode 100644 index 69cec7719337..000000000000 --- a/pkg/config/unmarshaler.go +++ /dev/null @@ -1,49 +0,0 @@ -package config - -import ( - "fmt" - - "go.opentelemetry.io/collector/confmap" -) - -// unmarshal converts a confmap.Conf into a Config struct. -// It splits the input confmap into a map of key-value pairs, fetches the corresponding -// signozconfmap.Config interface by name, merges it with the default config, validates it, -// and then creates a new confmap from the parsed map to unmarshal into the Config struct. -func unmarshal(conf *confmap.Conf) (*Config, error) { - raw := make(map[string]any) - if err := conf.Unmarshal(&raw); err != nil { - return nil, err - } - - parsed := make(map[string]any) - - // To help the defaults kick in, we need iterate over the default map instead of the raw values - for k, v := range defaults { - sub, err := conf.Sub(k) - if err != nil { - return nil, fmt.Errorf("cannot read config for %q: %w", k, err) - } - - d := v.NewWithDefaults() - if err := sub.Unmarshal(&d); err != nil { - return nil, fmt.Errorf("cannot merge config for %q: %w", k, err) - } - - err = d.Validate() - if err != nil { - return nil, fmt.Errorf("failed to validate config for for %q: %w", k, err) - } - - parsed[k] = d - } - - parsedConf := confmap.NewFromStringMap(parsed) - config := new(Config) - err := parsedConf.Unmarshal(config) - if err != nil { - return nil, fmt.Errorf("cannot unmarshal config: %w", err) - } - - return config, nil -} diff --git a/pkg/config/uri.go b/pkg/config/uri.go new file mode 100644 index 000000000000..ff1a2fb1ed52 --- /dev/null +++ b/pkg/config/uri.go @@ -0,0 +1,46 @@ +package config + +import ( + "fmt" + "regexp" +) + +var ( + // uriRegex is a regex that matches the URI format. It complies with the URI definition defined at https://datatracker.ietf.org/doc/html/rfc3986. + // The format is ":". + uriRegex = regexp.MustCompile(`(?s:^(?P[A-Za-z][A-Za-z0-9+.-]+):(?P.*)$)`) +) + +type Uri struct { + scheme string + value string +} + +func NewUri(input string) (Uri, error) { + submatches := uriRegex.FindStringSubmatch(input) + + if len(submatches) != 3 { + return Uri{}, fmt.Errorf("invalid uri: %q", input) + } + return Uri{ + scheme: submatches[1], + value: submatches[2], + }, nil +} + +func MustNewUri(input string) Uri { + uri, err := NewUri(input) + if err != nil { + panic(err) + } + + return uri +} + +func (uri Uri) Scheme() string { + return uri.scheme +} + +func (uri Uri) Value() string { + return uri.value +} diff --git a/pkg/config/uri_test.go b/pkg/config/uri_test.go new file mode 100644 index 000000000000..bbd95823eb5a --- /dev/null +++ b/pkg/config/uri_test.go @@ -0,0 +1,35 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewUri(t *testing.T) { + testCases := []struct { + input string + expected Uri + pass bool + }{ + {input: "file:/path/1", expected: Uri{scheme: "file", value: "/path/1"}, pass: true}, + {input: "file:", expected: Uri{scheme: "file", value: ""}, pass: true}, + {input: "env:", expected: Uri{scheme: "env", value: ""}, pass: true}, + {input: "scheme", expected: Uri{}, pass: false}, + } + + for _, tc := range testCases { + uri, err := NewUri(tc.input) + if !tc.pass { + assert.Error(t, err) + continue + } + + require.NoError(t, err) + assert.NotPanics(t, func() { MustNewUri(tc.input) }) + assert.Equal(t, tc.expected, uri) + assert.Equal(t, tc.expected.Scheme(), uri.scheme) + assert.Equal(t, tc.expected.Value(), uri.value) + } +} diff --git a/pkg/confmap/config.go b/pkg/confmap/config.go deleted file mode 100644 index f3425faf4458..000000000000 --- a/pkg/confmap/config.go +++ /dev/null @@ -1,9 +0,0 @@ -package confmap - -// Config is an interface that defines methods for creating and validating configurations. -type Config interface { - // New creates a new instance of the configuration with default values. - NewWithDefaults() Config - // Validate the configuration and returns an error if invalid. - Validate() error -} diff --git a/pkg/confmap/doc.go b/pkg/confmap/doc.go deleted file mode 100644 index de21ed5aa071..000000000000 --- a/pkg/confmap/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package confmap is a wrapper on top of the confmap defined here: -// https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go/ -package confmap diff --git a/pkg/confmap/provider/signozenvprovider/provider.go b/pkg/confmap/provider/signozenvprovider/provider.go deleted file mode 100644 index aee4c9b49990..000000000000 --- a/pkg/confmap/provider/signozenvprovider/provider.go +++ /dev/null @@ -1,94 +0,0 @@ -package signozenvprovider - -import ( - "context" - "fmt" - "os" - "regexp" - "sort" - "strings" - - "go.opentelemetry.io/collector/confmap" - "go.uber.org/zap" - "gopkg.in/yaml.v3" -) - -const ( - schemeName string = "signozenv" - envPrefix string = "signoz" - separator string = "__" - envPrefixWithOneSeparator string = "signoz_" - envRegexString string = `^[a-zA-Z][a-zA-Z0-9_]*$` -) - -var ( - envRegex = regexp.MustCompile(envRegexString) -) - -type provider struct { - logger *zap.Logger -} - -// NewFactory returns a factory for a confmap.Provider that reads the configuration from the environment. -// All variables starting with `SIGNOZ__` are read from the environment. -// The separator is `__` (2 underscores) in order to incorporate env variables having keys with a single `_` -func NewFactory() confmap.ProviderFactory { - return confmap.NewProviderFactory(newProvider) -} - -func newProvider(settings confmap.ProviderSettings) confmap.Provider { - return &provider{ - logger: settings.Logger, - } -} - -func (provider *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) { - if !strings.HasPrefix(uri, schemeName+":") { - return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName) - } - - // Read and Sort environment variables for consistent output - envvars := os.Environ() - sort.Strings(envvars) - - // Create a map m containing key value pairs - m := make(map[string]any) - for _, envvar := range envvars { - parts := strings.SplitN(envvar, "=", 2) - if len(parts) != 2 { - continue - } - key := strings.ToLower(parts[0]) - val := parts[1] - - if strings.HasPrefix(key, envPrefixWithOneSeparator) { - // Remove the envPrefix from the key - key = strings.Replace(key, envPrefix+separator, "", 1) - - // Check whether the resulting key matches with the regex - if !envRegex.MatchString(key) { - provider.logger.Warn("Configuration references invalid environment variable key", zap.String("key", key)) - continue - } - - // Convert key into yaml format - key = strings.ToLower(strings.ReplaceAll(key, separator, confmap.KeyDelimiter)) - m[key] = val - } - } - - out, err := yaml.Marshal(m) - if err != nil { - return nil, err - } - - return confmap.NewRetrievedFromYAML(out) -} - -func (*provider) Scheme() string { - return schemeName -} - -func (*provider) Shutdown(context.Context) error { - return nil -} diff --git a/pkg/confmap/provider/signozenvprovider/provider_test.go b/pkg/confmap/provider/signozenvprovider/provider_test.go deleted file mode 100644 index 5a0ef1ea5755..000000000000 --- a/pkg/confmap/provider/signozenvprovider/provider_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package signozenvprovider - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/confmap" - "go.opentelemetry.io/collector/confmap/confmaptest" -) - -func createProvider() confmap.Provider { - return NewFactory().Create(confmaptest.NewNopProviderSettings()) -} - -func TestValidateProviderScheme(t *testing.T) { - assert.NoError(t, confmaptest.ValidateProviderScheme(createProvider())) -} - -func TestRetrieve(t *testing.T) { - t.Setenv("SIGNOZ__STORAGE__DSN", "localhost:9000") - t.Setenv("SIGNOZ__SIGNOZ_ENABLED", "true") - t.Setenv("SIGNOZ__INSTRUMENTATION__LOGS__ENABLED", "true") - expected := confmap.NewFromStringMap(map[string]any{ - "storage::dsn": "localhost:9000", - "signoz_enabled": "true", - "instrumentation::logs::enabled": "true", - }) - - signoz := createProvider() - retrieved, err := signoz.Retrieve(context.Background(), schemeName+":", nil) - require.NoError(t, err) - - actual, err := retrieved.AsConf() - require.NoError(t, err) - - assert.Equal(t, expected.ToStringMap(), actual.ToStringMap()) - assert.NoError(t, signoz.Shutdown(context.Background())) -} diff --git a/pkg/factory/provider.go b/pkg/factory/provider.go index e035fb54593d..0993440da560 100644 --- a/pkg/factory/provider.go +++ b/pkg/factory/provider.go @@ -1,6 +1,8 @@ package factory -import "context" +import ( + "context" +) type Provider = any @@ -21,10 +23,17 @@ func (factory *providerFactory[P, C]) Name() Name { return factory.name } -func (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (P, error) { - return factory.newProviderFunc(ctx, settings, config) +func (factory *providerFactory[P, C]) New(ctx context.Context, settings ProviderSettings, config C) (p P, err error) { + provider, err := factory.newProviderFunc(ctx, settings, config) + if err != nil { + return + } + + p = provider + return } +// NewProviderFactory creates a new provider factory. func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProviderFunc[P, C]) ProviderFactory[P, C] { return &providerFactory[P, C]{ name: name, @@ -32,7 +41,8 @@ func NewProviderFactory[P Provider, C Config](name Name, newProviderFunc NewProv } } -func NewFromFactory[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) { +// NewProviderFromNamedMap creates a new provider from a factory based on the input key. +func NewProviderFromNamedMap[P Provider, C Config](ctx context.Context, settings ProviderSettings, config C, factories NamedMap[ProviderFactory[P, C]], key string) (p P, err error) { providerFactory, err := factories.Get(key) if err != nil { return diff --git a/pkg/factory/provider_test.go b/pkg/factory/provider_test.go index fad8fd1673a1..cd0dbfaf5435 100644 --- a/pkg/factory/provider_test.go +++ b/pkg/factory/provider_test.go @@ -32,10 +32,10 @@ func TestNewProviderFactoryFromFactory(t *testing.T) { m := MustNewNamedMap(pf) assert.Equal(t, MustNewName("p1"), pf.Name()) - p, err := NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p1") + p, err := NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p1") assert.NoError(t, err) assert.IsType(t, p1{}, p) - _, err = NewFromFactory(context.Background(), ProviderSettings{}, pc1{}, m, "p2") + _, err = NewProviderFromNamedMap(context.Background(), ProviderSettings{}, pc1{}, m, "p2") assert.Error(t, err) } diff --git a/pkg/factory/providertest/setting.go b/pkg/factory/providertest/setting.go new file mode 100644 index 000000000000..a42de9011561 --- /dev/null +++ b/pkg/factory/providertest/setting.go @@ -0,0 +1,10 @@ +package providertest + +import ( + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/instrumentation/instrumentationtest" +) + +func NewSettings() factory.ProviderSettings { + return instrumentationtest.New().ToProviderSettings() +} diff --git a/pkg/http/server/config.go b/pkg/http/server/config.go index fb8eb5be1165..aa02bb542f10 100644 --- a/pkg/http/server/config.go +++ b/pkg/http/server/config.go @@ -1,12 +1,5 @@ package server -import ( - "go.signoz.io/signoz/pkg/confmap" -) - -// Config satisfies the confmap.Config interface -var _ confmap.Config = (*Config)(nil) - // Config holds the configuration for http. type Config struct { //Address specifies the TCP address for the server to listen on, in the form "host:port". @@ -14,14 +7,3 @@ type Config struct { // See net.Dial for details of the address format. Address string `mapstructure:"address"` } - -func (c *Config) NewWithDefaults() confmap.Config { - return &Config{ - Address: "0.0.0.0:8080", - } - -} - -func (c *Config) Validate() error { - return nil -} diff --git a/pkg/http/server/server.go b/pkg/http/server/server.go index fbeca1c3a93a..50d268f24c5d 100644 --- a/pkg/http/server/server.go +++ b/pkg/http/server/server.go @@ -6,21 +6,20 @@ import ( "net/http" "time" - "go.signoz.io/signoz/pkg/registry" + "go.signoz.io/signoz/pkg/factory" "go.uber.org/zap" ) -var _ registry.NamedService = (*Server)(nil) +var _ factory.Service = (*Server)(nil) type Server struct { srv *http.Server logger *zap.Logger handler http.Handler cfg Config - name string } -func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Server, error) { +func New(logger *zap.Logger, cfg Config, handler http.Handler) (*Server, error) { if handler == nil { return nil, fmt.Errorf("cannot build http server, handler is required") } @@ -29,10 +28,6 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se return nil, fmt.Errorf("cannot build http server, logger is required") } - if name == "" { - return nil, fmt.Errorf("cannot build http server, name is required") - } - srv := &http.Server{ Addr: cfg.Address, Handler: handler, @@ -46,14 +41,9 @@ func New(logger *zap.Logger, name string, cfg Config, handler http.Handler) (*Se logger: logger.Named("go.signoz.io/pkg/http/server"), handler: handler, cfg: cfg, - name: name, }, nil } -func (server *Server) Name() string { - return server.name -} - func (server *Server) Start(ctx context.Context) error { server.logger.Info("starting http server", zap.String("address", server.srv.Addr)) if err := server.srv.ListenAndServe(); err != nil { diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index 98a135d788bd..cfddc0e4c3ad 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -34,6 +34,8 @@ import ( "go.signoz.io/signoz/pkg/query-service/app/preferences" "go.signoz.io/signoz/pkg/query-service/common" v3 "go.signoz.io/signoz/pkg/query-service/model/v3" + "go.signoz.io/signoz/pkg/signoz" + "go.signoz.io/signoz/pkg/web" "go.signoz.io/signoz/pkg/query-service/app/explorer" "go.signoz.io/signoz/pkg/query-service/auth" @@ -69,6 +71,7 @@ type ServerOptions struct { Cluster string UseLogsNewSchema bool UseTraceNewSchema bool + SigNoz *signoz.SigNoz } // Server runs HTTP, Mux and a grpc server @@ -218,7 +221,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { unavailableChannel: make(chan healthcheck.Status), } - httpServer, err := s.createPublicServer(apiHandler) + httpServer, err := s.createPublicServer(apiHandler, serverOptions.SigNoz.Web) if err != nil { return nil, err @@ -282,7 +285,7 @@ func (s *Server) createPrivateServer(api *APIHandler) (*http.Server, error) { }, nil } -func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) { +func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server, error) { r := NewRouter() @@ -327,6 +330,11 @@ func (s *Server) createPublicServer(api *APIHandler) (*http.Server, error) { handler = handlers.CompressHandler(handler) + err := web.AddToRouter(r) + if err != nil { + return nil, err + } + return &http.Server{ Handler: handler, }, nil diff --git a/pkg/query-service/main.go b/pkg/query-service/main.go index 1f1c54b961b2..ba475a05e3ed 100644 --- a/pkg/query-service/main.go +++ b/pkg/query-service/main.go @@ -9,11 +9,15 @@ import ( "time" prommodel "github.com/prometheus/common/model" + "go.signoz.io/signoz/pkg/config" + "go.signoz.io/signoz/pkg/config/envprovider" + "go.signoz.io/signoz/pkg/config/fileprovider" "go.signoz.io/signoz/pkg/query-service/app" "go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/migrate" "go.signoz.io/signoz/pkg/query-service/version" + "go.signoz.io/signoz/pkg/signoz" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -74,6 +78,22 @@ func main() { logger := loggerMgr.Sugar() version.PrintVersion() + config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{ + Uris: []string{"env:"}, + ProviderFactories: []config.ProviderFactory{ + envprovider.NewFactory(), + fileprovider.NewFactory(), + }, + }) + if err != nil { + zap.L().Fatal("Failed to create config", zap.Error(err)) + } + + signoz, err := signoz.New(context.Background(), config, signoz.NewProviderConfig()) + if err != nil { + zap.L().Fatal("Failed to create signoz struct", zap.Error(err)) + } + serverOptions := &app.ServerOptions{ HTTPHostPort: constants.HTTPHostPort, PromConfigPath: promConfigPath, @@ -90,6 +110,7 @@ func main() { Cluster: cluster, UseLogsNewSchema: useLogsNewSchema, UseTraceNewSchema: useTraceNewSchema, + SigNoz: signoz, } // Read the jwt secret key diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 850a4389a9c9..410044970bfa 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -8,18 +8,19 @@ import ( "os/signal" "syscall" + "go.signoz.io/signoz/pkg/factory" "go.uber.org/zap" ) type Registry struct { - services []NamedService + services []factory.Service logger *zap.Logger startCh chan error stopCh chan error } // New creates a new registry of services. It needs at least one service in the input. -func New(logger *zap.Logger, services ...NamedService) (*Registry, error) { +func New(logger *zap.Logger, services ...factory.Service) (*Registry, error) { if logger == nil { return nil, fmt.Errorf("cannot build registry, logger is required") } @@ -38,7 +39,7 @@ func New(logger *zap.Logger, services ...NamedService) (*Registry, error) { func (r *Registry) Start(ctx context.Context) error { for _, s := range r.services { - go func(s Service) { + go func(s factory.Service) { err := s.Start(ctx) r.startCh <- err }(s) @@ -66,7 +67,7 @@ func (r *Registry) Wait(ctx context.Context) error { func (r *Registry) Stop(ctx context.Context) error { for _, s := range r.services { - go func(s Service) { + go func(s factory.Service) { err := s.Stop(ctx) r.stopCh <- err }(s) diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 12ae1d88627f..2fb46e28db7d 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -6,14 +6,15 @@ import ( "testing" "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/factory/servicetest" "go.uber.org/zap" ) func TestRegistryWith2HttpServers(t *testing.T) { - http1, err := newHttpService("http1") + http1, err := servicetest.NewHttpService("http1") require.NoError(t, err) - http2, err := newHttpService("http2") + http2, err := servicetest.NewHttpService("http2") require.NoError(t, err) registry, err := New(zap.NewNop(), http1, http2) @@ -34,10 +35,10 @@ func TestRegistryWith2HttpServers(t *testing.T) { } func TestRegistryWith2HttpServersWithoutWait(t *testing.T) { - http1, err := newHttpService("http1") + http1, err := servicetest.NewHttpService("http1") require.NoError(t, err) - http2, err := newHttpService("http2") + http2, err := servicetest.NewHttpService("http2") require.NoError(t, err) registry, err := New(zap.NewNop(), http1, http2) diff --git a/pkg/registry/service.go b/pkg/registry/service.go deleted file mode 100644 index 38df4f0a4fb2..000000000000 --- a/pkg/registry/service.go +++ /dev/null @@ -1,16 +0,0 @@ -package registry - -import "context" - -type Service interface { - // Starts a service. The service should return an error if it cannot be started. - Start(context.Context) error - // Stops a service. - Stop(context.Context) error -} - -type NamedService interface { - // Identifier of a service. It should be unique across all services. - Name() string - Service -} diff --git a/pkg/registry/service_test.go b/pkg/registry/service_test.go deleted file mode 100644 index dc0621e962fe..000000000000 --- a/pkg/registry/service_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package registry - -import ( - "context" - "net" - "net/http" -) - -var _ NamedService = (*httpService)(nil) - -type httpService struct { - Listener net.Listener - Server *http.Server - name string -} - -func newHttpService(name string) (*httpService, error) { - return &httpService{ - name: name, - Server: &http.Server{}, - }, nil -} - -func (service *httpService) Name() string { - return service.name -} - -func (service *httpService) Start(ctx context.Context) error { - listener, err := net.Listen("tcp", "localhost:0") - if err != nil { - return err - } - service.Listener = listener - - if err := service.Server.Serve(service.Listener); err != nil { - if err != http.ErrServerClosed { - return err - } - } - return nil -} - -func (service *httpService) Stop(ctx context.Context) error { - if err := service.Server.Shutdown(ctx); err != nil { - return err - } - - return nil -} diff --git a/pkg/signoz/config.go b/pkg/signoz/config.go new file mode 100644 index 000000000000..4b0041d2714f --- /dev/null +++ b/pkg/signoz/config.go @@ -0,0 +1,53 @@ +package signoz + +import ( + "context" + + "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/config" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/instrumentation" + "go.signoz.io/signoz/pkg/sqlmigrator" + "go.signoz.io/signoz/pkg/sqlstore" + "go.signoz.io/signoz/pkg/web" +) + +// Config defines the entire input configuration of signoz. +type Config struct { + // Instrumentation config + Instrumentation instrumentation.Config `mapstructure:"instrumentation"` + + // Web config + Web web.Config `mapstructure:"web"` + + // Cache config + Cache cache.Config `mapstructure:"cache"` + + // SQLStore config + SQLStore sqlstore.Config `mapstructure:"sqlstore"` + + // SQLMigrator config + SQLMigrator sqlmigrator.Config `mapstructure:"sqlmigrator"` +} + +func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig) (Config, error) { + configFactories := []factory.ConfigFactory{ + instrumentation.NewConfigFactory(), + web.NewConfigFactory(), + cache.NewConfigFactory(), + sqlstore.NewConfigFactory(), + sqlmigrator.NewConfigFactory(), + } + + conf, err := config.New(ctx, resolverConfig, configFactories) + if err != nil { + return Config{}, err + } + + var config Config + if err := conf.Unmarshal("", &config); err != nil { + return Config{}, err + } + + return config, nil +} diff --git a/pkg/signoz/provider.go b/pkg/signoz/provider.go new file mode 100644 index 000000000000..d54d1d73bc4d --- /dev/null +++ b/pkg/signoz/provider.go @@ -0,0 +1,54 @@ +package signoz + +import ( + "go.signoz.io/signoz/pkg/cache" + "go.signoz.io/signoz/pkg/cache/memorycache" + "go.signoz.io/signoz/pkg/cache/rediscache" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/sqlmigration" + "go.signoz.io/signoz/pkg/sqlstore" + "go.signoz.io/signoz/pkg/sqlstore/sqlitesqlstore" + "go.signoz.io/signoz/pkg/web" + "go.signoz.io/signoz/pkg/web/noopweb" + "go.signoz.io/signoz/pkg/web/routerweb" +) + +type ProviderConfig struct { + // Map of all cache provider factories + CacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]] + + // Map of all web provider factories + WebProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]] + + // Map of all sqlstore provider factories + SQLStoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]] + + // Map of all sql migration provider factories + SQLMigrationProviderFactories factory.NamedMap[factory.ProviderFactory[sqlmigration.SQLMigration, sqlmigration.Config]] +} + +func NewProviderConfig() ProviderConfig { + return ProviderConfig{ + CacheProviderFactories: factory.MustNewNamedMap( + memorycache.NewFactory(), + rediscache.NewFactory(), + ), + WebProviderFactories: factory.MustNewNamedMap( + routerweb.NewFactory(), + noopweb.NewFactory(), + ), + SQLStoreProviderFactories: factory.MustNewNamedMap( + sqlitesqlstore.NewFactory(), + ), + SQLMigrationProviderFactories: factory.MustNewNamedMap( + sqlmigration.NewAddDataMigrationsFactory(), + sqlmigration.NewAddOrganizationFactory(), + sqlmigration.NewAddPreferencesFactory(), + sqlmigration.NewAddDashboardsFactory(), + sqlmigration.NewAddSavedViewsFactory(), + sqlmigration.NewAddAgentsFactory(), + sqlmigration.NewAddPipelinesFactory(), + sqlmigration.NewAddIntegrationsFactory(), + ), + } +} diff --git a/pkg/signoz/provider_test.go b/pkg/signoz/provider_test.go new file mode 100644 index 000000000000..82ee0d655975 --- /dev/null +++ b/pkg/signoz/provider_test.go @@ -0,0 +1,16 @@ +package signoz + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewProviderConfig(t *testing.T) { + // This is a test to ensure that provider factories can be created without panicking since + // we are using the factory.MustNewNamedMap function to initialize the provider factories. + // It also helps us catch these errors during testing instead of runtime. + assert.NotPanics(t, func() { + NewProviderConfig() + }) +} diff --git a/pkg/signoz/signoz.go b/pkg/signoz/signoz.go index 6273dd5eabb5..ce31a349cff3 100644 --- a/pkg/signoz/signoz.go +++ b/pkg/signoz/signoz.go @@ -1,13 +1,14 @@ package signoz import ( + "context" + "go.signoz.io/signoz/pkg/cache" - "go.signoz.io/signoz/pkg/cache/memorycache" - "go.signoz.io/signoz/pkg/cache/rediscache" - "go.signoz.io/signoz/pkg/config" + "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/instrumentation" + "go.signoz.io/signoz/pkg/version" + "go.signoz.io/signoz/pkg/web" - "go.signoz.io/signoz/pkg/web/routerweb" - "go.uber.org/zap" ) type SigNoz struct { @@ -15,19 +16,41 @@ type SigNoz struct { Web web.Web } -func New(config *config.Config, skipWebFrontend bool) (*SigNoz, error) { - var cache cache.Cache - - // init for the cache - switch config.Cache.Provider { - case "memory": - cache = memorycache.New(&config.Cache.Memory) - case "redis": - cache = rediscache.New(&config.Cache.Redis) +func New( + ctx context.Context, + config Config, + providerConfig ProviderConfig, +) (*SigNoz, error) { + // Initialize instrumentation + instrumentation, err := instrumentation.New(ctx, version.Build{}, config.Instrumentation) + if err != nil { + return nil, err } - web, err := routerweb.New(zap.L(), config.Web) - if err != nil && !skipWebFrontend { + // Get the provider settings from instrumentation + providerSettings := instrumentation.ToProviderSettings() + + // Initialize cache from the available cache provider factories + cache, err := factory.NewProviderFromNamedMap( + ctx, + providerSettings, + config.Cache, + providerConfig.CacheProviderFactories, + config.Cache.Provider, + ) + if err != nil { + return nil, err + } + + // Initialize web from the available web provider factories + web, err := factory.NewProviderFromNamedMap( + ctx, + providerSettings, + config.Web, + providerConfig.WebProviderFactories, + config.Web.Provider(), + ) + if err != nil { return nil, err } diff --git a/pkg/sqlmigrator/migration/000_add_data_migrations.go b/pkg/sqlmigration/000_add_data_migrations.go similarity index 81% rename from pkg/sqlmigrator/migration/000_add_data_migrations.go rename to pkg/sqlmigration/000_add_data_migrations.go index 516cd7e6dcdb..6b138329bed4 100644 --- a/pkg/sqlmigrator/migration/000_add_data_migrations.go +++ b/pkg/sqlmigration/000_add_data_migrations.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addDataMigrations struct{} -func NewAddDataMigrationsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddDataMigrationsFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_data_migrations"), newAddDataMigrations) } -func newAddDataMigrations(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddDataMigrations(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addDataMigrations{}, nil } diff --git a/pkg/sqlmigrator/migration/001_add_organization.go b/pkg/sqlmigration/001_add_organization.go similarity index 93% rename from pkg/sqlmigrator/migration/001_add_organization.go rename to pkg/sqlmigration/001_add_organization.go index c26ea4dc6094..158fa5ecee8a 100644 --- a/pkg/sqlmigrator/migration/001_add_organization.go +++ b/pkg/sqlmigration/001_add_organization.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addOrganization struct{} -func NewAddOrganizationFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddOrganizationFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_organization"), newAddOrganization) } -func newAddOrganization(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddOrganization(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addOrganization{}, nil } diff --git a/pkg/sqlmigrator/migration/002_add_preferences.go b/pkg/sqlmigration/002_add_preferences.go similarity index 86% rename from pkg/sqlmigrator/migration/002_add_preferences.go rename to pkg/sqlmigration/002_add_preferences.go index 23f7773991c1..436d7826fee4 100644 --- a/pkg/sqlmigrator/migration/002_add_preferences.go +++ b/pkg/sqlmigration/002_add_preferences.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addPreferences struct{} -func NewAddPreferencesFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddPreferencesFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_preferences"), newAddPreferences) } -func newAddPreferences(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddPreferences(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addPreferences{}, nil } diff --git a/pkg/sqlmigrator/migration/003_add_dashboards.go b/pkg/sqlmigration/003_add_dashboards.go similarity index 76% rename from pkg/sqlmigrator/migration/003_add_dashboards.go rename to pkg/sqlmigration/003_add_dashboards.go index 65f45ae1e645..2424b8923950 100644 --- a/pkg/sqlmigrator/migration/003_add_dashboards.go +++ b/pkg/sqlmigration/003_add_dashboards.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addDashboards struct{} -func NewAddDashboardsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddDashboardsFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_dashboards"), newAddDashboards) } -func newAddDashboards(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddDashboards(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addDashboards{}, nil } @@ -96,8 +95,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("rules"). ColumnExpr("created_at datetime"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "created_at")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "rules", "created_at")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } @@ -106,8 +105,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("rules"). ColumnExpr("created_by TEXT"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "created_by")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "rules", "created_by")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } @@ -116,8 +115,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("rules"). ColumnExpr("updated_by TEXT"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "rules", "updated_by")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "rules", "updated_by")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } @@ -126,8 +125,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("dashboards"). ColumnExpr("created_by TEXT"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "created_by")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "dashboards", "created_by")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } @@ -136,8 +135,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("dashboards"). ColumnExpr("updated_by TEXT"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "updated_by")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "dashboards", "updated_by")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } @@ -146,8 +145,8 @@ func (migration *addDashboards) Up(ctx context.Context, db *bun.DB) error { NewAddColumn(). Table("dashboards"). ColumnExpr("locked INTEGER DEFAULT 0"). - Apply(sqlmigrator.WrapIfNotExists(ctx, db, "dashboards", "locked")). - Exec(ctx); err != nil && err != sqlmigrator.ErrNoExecute { + Apply(WrapIfNotExists(ctx, db, "dashboards", "locked")). + Exec(ctx); err != nil && err != ErrNoExecute { return err } diff --git a/pkg/sqlmigrator/migration/004_add_saved_views.go b/pkg/sqlmigration/004_add_saved_views.go similarity index 83% rename from pkg/sqlmigrator/migration/004_add_saved_views.go rename to pkg/sqlmigration/004_add_saved_views.go index 0f97be3f29bc..bec921c23b6b 100644 --- a/pkg/sqlmigrator/migration/004_add_saved_views.go +++ b/pkg/sqlmigration/004_add_saved_views.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addSavedViews struct{} -func NewAddSavedViewsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddSavedViewsFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_saved_views"), newAddSavedViews) } -func newAddSavedViews(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddSavedViews(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addSavedViews{}, nil } diff --git a/pkg/sqlmigrator/migration/005_add_agents.go b/pkg/sqlmigration/005_add_agents.go similarity index 91% rename from pkg/sqlmigrator/migration/005_add_agents.go rename to pkg/sqlmigration/005_add_agents.go index ede646f63c2a..dac816872718 100644 --- a/pkg/sqlmigrator/migration/005_add_agents.go +++ b/pkg/sqlmigration/005_add_agents.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addAgents struct{} -func NewAddAgentsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddAgentsFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_agents"), newAddAgents) } -func newAddAgents(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddAgents(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addAgents{}, nil } diff --git a/pkg/sqlmigrator/migration/006_add_pipelines.go b/pkg/sqlmigration/006_add_pipelines.go similarity index 82% rename from pkg/sqlmigrator/migration/006_add_pipelines.go rename to pkg/sqlmigration/006_add_pipelines.go index 82fa19ac3dec..897475477570 100644 --- a/pkg/sqlmigrator/migration/006_add_pipelines.go +++ b/pkg/sqlmigration/006_add_pipelines.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addPipelines struct{} -func NewAddPipelinesFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddPipelinesFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_pipelines"), newAddPipelines) } -func newAddPipelines(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddPipelines(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addPipelines{}, nil } diff --git a/pkg/sqlmigrator/migration/007_add_integrations.go b/pkg/sqlmigration/007_add_integrations.go similarity index 85% rename from pkg/sqlmigrator/migration/007_add_integrations.go rename to pkg/sqlmigration/007_add_integrations.go index 571a2d90fe36..2cc7fab6646e 100644 --- a/pkg/sqlmigrator/migration/007_add_integrations.go +++ b/pkg/sqlmigration/007_add_integrations.go @@ -1,4 +1,4 @@ -package migration +package sqlmigration import ( "context" @@ -6,16 +6,15 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" - "go.signoz.io/signoz/pkg/sqlmigrator" ) type addIntegrations struct{} -func NewAddIntegrationsFactory() factory.ProviderFactory[sqlmigrator.SQLMigration, sqlmigrator.Config] { +func NewAddIntegrationsFactory() factory.ProviderFactory[SQLMigration, Config] { return factory.NewProviderFactory(factory.MustNewName("add_integrations"), newAddIntegrations) } -func newAddIntegrations(_ context.Context, _ factory.ProviderSettings, _ sqlmigrator.Config) (sqlmigrator.SQLMigration, error) { +func newAddIntegrations(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) { return &addIntegrations{}, nil } diff --git a/pkg/sqlmigration/config.go b/pkg/sqlmigration/config.go new file mode 100644 index 000000000000..aa1e9da69bfb --- /dev/null +++ b/pkg/sqlmigration/config.go @@ -0,0 +1,19 @@ +package sqlmigration + +import ( + "go.signoz.io/signoz/pkg/factory" +) + +type Config struct{} + +func NewConfigFactory() factory.ConfigFactory { + return factory.NewConfigFactory(factory.MustNewName("sqlmigration"), newConfig) +} + +func newConfig() factory.Config { + return Config{} +} + +func (c Config) Validate() error { + return nil +} diff --git a/pkg/sqlmigrator/migration.go b/pkg/sqlmigration/sqlmigration.go similarity index 73% rename from pkg/sqlmigrator/migration.go rename to pkg/sqlmigration/sqlmigration.go index eeec2d1d89d3..d0b121f6acdd 100644 --- a/pkg/sqlmigrator/migration.go +++ b/pkg/sqlmigration/sqlmigration.go @@ -1,4 +1,4 @@ -package sqlmigrator +package sqlmigration import ( "context" @@ -11,11 +11,22 @@ import ( "go.signoz.io/signoz/pkg/factory" ) +// SQLMigration is the interface for a single migration. +type SQLMigration interface { + // Register registers the migration with the given migrations. Each migration needs to be registered + //in a dedicated `*.go` file so that the correct migration semantics can be detected. + Register(*migrate.Migrations) error + // Up runs the migration. + Up(context.Context, *bun.DB) error + // Down rolls back the migration. + Down(context.Context, *bun.DB) error +} + var ( ErrNoExecute = errors.New("no execute") ) -func NewMigrations( +func New( ctx context.Context, settings factory.ProviderSettings, config Config, @@ -38,13 +49,13 @@ func NewMigrations( return migrations, nil } -func MustNewMigrations( +func MustNew( ctx context.Context, settings factory.ProviderSettings, config Config, factories factory.NamedMap[factory.ProviderFactory[SQLMigration, Config]], ) *migrate.Migrations { - migrations, err := NewMigrations(ctx, settings, config, factories) + migrations, err := New(ctx, settings, config, factories) if err != nil { panic(err) } diff --git a/pkg/sqlmigrator/000_noop.go b/pkg/sqlmigration/sqlmigrationtest/000_noop.go similarity index 69% rename from pkg/sqlmigrator/000_noop.go rename to pkg/sqlmigration/sqlmigrationtest/000_noop.go index 950b52c4b5a7..ac0b246c9366 100644 --- a/pkg/sqlmigrator/000_noop.go +++ b/pkg/sqlmigration/sqlmigrationtest/000_noop.go @@ -1,4 +1,4 @@ -package sqlmigrator +package sqlmigrationtest import ( "context" @@ -6,12 +6,13 @@ import ( "github.com/uptrace/bun" "github.com/uptrace/bun/migrate" "go.signoz.io/signoz/pkg/factory" + "go.signoz.io/signoz/pkg/sqlmigration" ) type noopMigration struct{} -func NoopMigrationFactory() factory.ProviderFactory[SQLMigration, Config] { - return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) { +func NoopMigrationFactory() factory.ProviderFactory[sqlmigration.SQLMigration, sqlmigration.Config] { + return factory.NewProviderFactory(factory.MustNewName("noop"), func(_ context.Context, _ factory.ProviderSettings, _ sqlmigration.Config) (sqlmigration.SQLMigration, error) { return &noopMigration{}, nil }) } diff --git a/pkg/sqlmigrator/migrator_test.go b/pkg/sqlmigrator/migrator_test.go index 7449e3b00f91..67197e3d3c67 100644 --- a/pkg/sqlmigrator/migrator_test.go +++ b/pkg/sqlmigrator/migrator_test.go @@ -10,6 +10,8 @@ import ( "github.com/stretchr/testify/require" "go.signoz.io/signoz/pkg/factory" "go.signoz.io/signoz/pkg/instrumentation/instrumentationtest" + "go.signoz.io/signoz/pkg/sqlmigration" + "go.signoz.io/signoz/pkg/sqlmigration/sqlmigrationtest" "go.signoz.io/signoz/pkg/sqlstore" "go.signoz.io/signoz/pkg/sqlstore/sqlstoretest" ) @@ -33,7 +35,7 @@ func TestMigratorWithSqliteAndNoopMigration(t *testing.T) { ctx, providerSettings, sqlstore, - MustNewMigrations(ctx, providerSettings, migrationConfig, factory.MustNewNamedMap(NoopMigrationFactory())), + sqlmigration.MustNew(ctx, providerSettings, sqlmigration.Config{}, factory.MustNewNamedMap(sqlmigrationtest.NoopMigrationFactory())), migrationConfig, ) diff --git a/pkg/sqlmigrator/sqlmigrator.go b/pkg/sqlmigrator/sqlmigrator.go index ce3f5e43c192..bc66fedd1cd4 100644 --- a/pkg/sqlmigrator/sqlmigrator.go +++ b/pkg/sqlmigrator/sqlmigrator.go @@ -2,9 +2,6 @@ package sqlmigrator import ( "context" - - "github.com/uptrace/bun" - "github.com/uptrace/bun/migrate" ) // SQLMigrator is the interface for the SQLMigrator. @@ -14,14 +11,3 @@ type SQLMigrator interface { // Rollback rolls back the database. Rollback acquires a lock on the database and rolls back the migrations. Rollback(context.Context) error } - -// SQLMigration is the interface for a single migration. -type SQLMigration interface { - // Register registers the migration with the given migrations. Each migration needs to be registered - //in a dedicated `*.go` file so that the correct migration semantics can be detected. - Register(*migrate.Migrations) error - // Up runs the migration. - Up(context.Context, *bun.DB) error - // Down rolls back the migration. - Down(context.Context, *bun.DB) error -} diff --git a/pkg/web/config.go b/pkg/web/config.go index a0e0c531de02..1baa320992b9 100644 --- a/pkg/web/config.go +++ b/pkg/web/config.go @@ -1,14 +1,13 @@ package web import ( - "go.signoz.io/signoz/pkg/confmap" + "go.signoz.io/signoz/pkg/factory" ) -// Config satisfies the confmap.Config interface -var _ confmap.Config = (*Config)(nil) - // Config holds the configuration for web. type Config struct { + // Whether the web package is enabled. + Enabled bool `mapstructure:"enabled"` // The prefix to serve the files from Prefix string `mapstructure:"prefix"` // The directory containing the static build files. The root of this directory should @@ -16,14 +15,26 @@ type Config struct { Directory string `mapstructure:"directory"` } -func (c *Config) NewWithDefaults() confmap.Config { +func NewConfigFactory() factory.ConfigFactory { + return factory.NewConfigFactory(factory.MustNewName("web"), newConfig) +} + +func newConfig() factory.Config { return &Config{ + Enabled: true, Prefix: "/", Directory: "/etc/signoz/web", } - } -func (c *Config) Validate() error { +func (c Config) Validate() error { return nil } + +func (c Config) Provider() string { + if c.Enabled { + return "router" + } + + return "noop" +} diff --git a/pkg/web/config_test.go b/pkg/web/config_test.go new file mode 100644 index 000000000000..793135fad854 --- /dev/null +++ b/pkg/web/config_test.go @@ -0,0 +1,45 @@ +package web + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/config" + "go.signoz.io/signoz/pkg/config/envprovider" + "go.signoz.io/signoz/pkg/factory" +) + +func TestNewWithEnvProvider(t *testing.T) { + t.Setenv("SIGNOZ_WEB_PREFIX", "/web") + t.Setenv("SIGNOZ_WEB_ENABLED", "false") + + conf, err := config.New( + context.Background(), + config.ResolverConfig{ + Uris: []string{"env:"}, + ProviderFactories: []config.ProviderFactory{ + envprovider.NewFactory(), + }, + }, + []factory.ConfigFactory{ + NewConfigFactory(), + }, + ) + require.NoError(t, err) + + actual := &Config{} + err = conf.Unmarshal("web", actual) + require.NoError(t, err) + + def := NewConfigFactory().New().(*Config) + + expected := &Config{ + Enabled: false, + Prefix: "/web", + Directory: def.Directory, + } + + assert.Equal(t, expected, actual) +} diff --git a/pkg/web/noopweb/provider.go b/pkg/web/noopweb/provider.go index 3b7af16b9ad3..9603872eaefe 100644 --- a/pkg/web/noopweb/provider.go +++ b/pkg/web/noopweb/provider.go @@ -11,6 +11,10 @@ import ( type provider struct{} +func NewFactory() factory.ProviderFactory[web.Web, web.Config] { + return factory.NewProviderFactory(factory.MustNewName("noop"), New) +} + func New(ctx context.Context, settings factory.ProviderSettings, config web.Config) (web.Web, error) { return &provider{}, nil } diff --git a/pkg/web/routerweb/provider.go b/pkg/web/routerweb/provider.go index f4cc644b0cb5..a4a85c26c7a3 100644 --- a/pkg/web/routerweb/provider.go +++ b/pkg/web/routerweb/provider.go @@ -1,6 +1,7 @@ package routerweb import ( + "context" "fmt" "net/http" "os" @@ -8,26 +9,25 @@ import ( "time" "github.com/gorilla/mux" + "go.signoz.io/signoz/pkg/factory" "go.signoz.io/signoz/pkg/http/middleware" "go.signoz.io/signoz/pkg/web" - "go.uber.org/zap" ) const ( indexFileName string = "index.html" ) -type Web struct { - logger *zap.Logger - cfg web.Config +type provider struct { + config web.Config } -func New(logger *zap.Logger, cfg web.Config) (*Web, error) { - if logger == nil { - return nil, fmt.Errorf("cannot build web, logger is required") - } +func NewFactory() factory.ProviderFactory[web.Web, web.Config] { + return factory.NewProviderFactory(factory.MustNewName("router"), New) +} - fi, err := os.Stat(cfg.Directory) +func New(ctx context.Context, settings factory.ProviderSettings, config web.Config) (web.Web, error) { + fi, err := os.Stat(config.Directory) if err != nil { return nil, fmt.Errorf("cannot access web directory: %w", err) } @@ -37,7 +37,7 @@ func New(logger *zap.Logger, cfg web.Config) (*Web, error) { return nil, fmt.Errorf("web directory is not a directory") } - fi, err = os.Stat(filepath.Join(cfg.Directory, indexFileName)) + fi, err = os.Stat(filepath.Join(config.Directory, indexFileName)) if err != nil { return nil, fmt.Errorf("cannot access %q in web directory: %w", indexFileName, err) } @@ -46,19 +46,18 @@ func New(logger *zap.Logger, cfg web.Config) (*Web, error) { return nil, fmt.Errorf("%q does not exist", indexFileName) } - return &Web{ - logger: logger.Named("go.signoz.io/pkg/web"), - cfg: cfg, + return &provider{ + config: config, }, nil } -func (web *Web) AddToRouter(router *mux.Router) error { +func (provider *provider) AddToRouter(router *mux.Router) error { cache := middleware.NewCache(7 * 24 * time.Hour) - err := router.PathPrefix(web.cfg.Prefix). + err := router.PathPrefix(provider.config.Prefix). Handler( http.StripPrefix( - web.cfg.Prefix, - cache.Wrap(http.HandlerFunc(web.ServeHTTP)), + provider.config.Prefix, + cache.Wrap(http.HandlerFunc(provider.ServeHTTP)), ), ).GetError() if err != nil { @@ -68,15 +67,15 @@ func (web *Web) AddToRouter(router *mux.Router) error { return nil } -func (web *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) { +func (provider *provider) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // Join internally call path.Clean to prevent directory traversal - path := filepath.Join(web.cfg.Directory, req.URL.Path) + path := filepath.Join(provider.config.Directory, req.URL.Path) // check whether a file exists or is a directory at the given path fi, err := os.Stat(path) if os.IsNotExist(err) || fi.IsDir() { // file does not exist or path is a directory, serve index.html - http.ServeFile(rw, req, filepath.Join(web.cfg.Directory, indexFileName)) + http.ServeFile(rw, req, filepath.Join(provider.config.Directory, indexFileName)) return } @@ -89,5 +88,5 @@ func (web *Web) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } // otherwise, use http.FileServer to serve the static file - http.FileServer(http.Dir(web.cfg.Directory)).ServeHTTP(rw, req) + http.FileServer(http.Dir(provider.config.Directory)).ServeHTTP(rw, req) } diff --git a/pkg/web/routerweb/provider_test.go b/pkg/web/routerweb/provider_test.go index a63c3e90c3f8..554cde3248a8 100644 --- a/pkg/web/routerweb/provider_test.go +++ b/pkg/web/routerweb/provider_test.go @@ -1,6 +1,7 @@ package routerweb import ( + "context" "io" "net" "net/http" @@ -11,8 +12,8 @@ import ( "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.signoz.io/signoz/pkg/factory/providertest" "go.signoz.io/signoz/pkg/web" - "go.uber.org/zap" ) func TestServeHttpWithoutPrefix(t *testing.T) { @@ -23,7 +24,7 @@ func TestServeHttpWithoutPrefix(t *testing.T) { expected, err := io.ReadAll(fi) require.NoError(t, err) - web, err := New(zap.NewNop(), web.Config{Prefix: "/", Directory: filepath.Join("testdata")}) + web, err := New(context.Background(), providertest.NewSettings(), web.Config{Prefix: "/", Directory: filepath.Join("testdata")}) require.NoError(t, err) router := mux.NewRouter() @@ -88,7 +89,7 @@ func TestServeHttpWithPrefix(t *testing.T) { expected, err := io.ReadAll(fi) require.NoError(t, err) - web, err := New(zap.NewNop(), web.Config{Prefix: "/web", Directory: filepath.Join("testdata")}) + web, err := New(context.Background(), providertest.NewSettings(), web.Config{Prefix: "/web", Directory: filepath.Join("testdata")}) require.NoError(t, err) router := mux.NewRouter()