diff --git a/go.mod b/go.mod index c0fb2b2..84f9b03 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/rs/cors v1.7.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.10.1 + github.com/statsig-io/go-sdk v1.26.0 github.com/stretchr/testify v1.8.4 github.com/twmb/franz-go v1.17.0 github.com/twmb/franz-go/pkg/kmsg v1.8.0 @@ -99,8 +100,10 @@ require ( github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/statsig-io/ip3country-go v0.2.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect github.com/tinylib/msgp v1.1.8 // indirect + github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.20.0 // indirect diff --git a/go.sum b/go.sum index a635b08..f524277 100644 --- a/go.sum +++ b/go.sum @@ -1909,6 +1909,10 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= +github.com/statsig-io/go-sdk v1.26.0 h1:HaPwN1+mDFdCnvqDbo8cr3obIlqmrShR9J2ogxw3kZs= +github.com/statsig-io/go-sdk v1.26.0/go.mod h1:Pej0D6R75gTHj7FdS6pbXQ7ayF0HL1cwOgiz5zDNdyc= +github.com/statsig-io/ip3country-go v0.2.0 h1:4z4ovVCx7GnQAKJC753bjcOgxLQJFsrDdcCKda4I2U8= +github.com/statsig-io/ip3country-go v0.2.0/go.mod h1:PKuA/VSpe4puBXw3BNGAHyP8IOZOiXAh/xIz+iYYoMQ= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1961,6 +1965,8 @@ github.com/twmb/franz-go v1.17.0 h1:hawgCx5ejDHkLe6IwAtFWwxi3OU4OztSTl7ZV5rwkYk= github.com/twmb/franz-go v1.17.0/go.mod h1:NreRdJ2F7dziDY/m6VyspWd6sNxHKXdMZI42UfQ3GXM= github.com/twmb/franz-go/pkg/kmsg v1.8.0 h1:lAQB9Z3aMrIP9qF9288XcFf/ccaSxEitNA1CDTEIeTA= github.com/twmb/franz-go/pkg/kmsg v1.8.0/go.mod h1:HzYEb8G3uu5XevZbtU0dVbkphaKTHk0X68N5ka4q6mU= +github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8= +github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 6d5cc13..9188d40 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -11,6 +11,7 @@ import ( logger "github.com/scribd/go-sdk/pkg/logger" "github.com/scribd/go-sdk/pkg/pubsub" server "github.com/scribd/go-sdk/pkg/server" + "github.com/scribd/go-sdk/pkg/statsig" tracking "github.com/scribd/go-sdk/pkg/tracking" ) @@ -25,6 +26,7 @@ type Config struct { PubSub *pubsub.Config Cache *cache.Config AWS *aws.Config + Statsig *statsig.Config } // NewConfig returns a new Config instance @@ -77,6 +79,11 @@ func NewConfig() (*Config, error) { errGroup = wrapErrors(errGroup, fmt.Errorf("aws config err: %w", err)) } + statsigConfig, err := statsig.NewConfig() + if err != nil { + errGroup = wrapErrors(errGroup, fmt.Errorf("statsig config err: %w", err)) + } + config.App = appConfig config.Database = dbConfig config.Instrumentation = instrumentationConfig @@ -86,6 +93,7 @@ func NewConfig() (*Config, error) { config.PubSub = pubsubConfig config.Cache = cacheConfig config.AWS = awsConfig + config.Statsig = statsigConfig return config, errGroup } diff --git a/pkg/statsig/config.go b/pkg/statsig/config.go new file mode 100644 index 0000000..5676a31 --- /dev/null +++ b/pkg/statsig/config.go @@ -0,0 +1,38 @@ +package statsig + +import ( + "fmt" + "os" + "time" + + cbuilder "github.com/scribd/go-sdk/internal/pkg/configuration/builder" +) + +// Config stores the configuration for the statsig. +type Config struct { + SecretKey string `mapstructure:"secret_key"` + LocalMode bool `mapstructure:"local_mode"` + ConfigSyncInterval time.Duration `mapstructure:"config_sync_interval"` + IDListSyncInterval time.Duration `mapstructure:"id_list_sync_interval"` + + environment string +} + +// NewConfig returns a new StatsigConfig instance. +func NewConfig() (*Config, error) { + config := &Config{} + config.environment = os.Getenv("APP_ENV") + + viperBuilder := cbuilder.New("statsig") + + vConf, err := viperBuilder.Build() + if err != nil { + return config, err + } + + if err = vConf.Unmarshal(config); err != nil { + return config, fmt.Errorf("unable to decode into struct: %s", err.Error()) + } + + return config, nil +} diff --git a/pkg/statsig/config_test.go b/pkg/statsig/config_test.go new file mode 100644 index 0000000..8e5ff63 --- /dev/null +++ b/pkg/statsig/config_test.go @@ -0,0 +1,49 @@ +package statsig + +import ( + "os" + "testing" + "time" + + assert "github.com/stretchr/testify/assert" +) + +func TestNewConfig(t *testing.T) { + t.Run("RunningInTestEnvironment", func(t *testing.T) { + expected := "test" + actual := os.Getenv("APP_ENV") + assert.Equal(t, expected, actual) + }) + + testCases := []struct { + name string + secretKey string + localMode bool + configSyncInterval time.Duration + idListSyncInterval time.Duration + wantError bool + }{ + { + name: "default", + secretKey: "", + localMode: false, + configSyncInterval: 0, + idListSyncInterval: 0, + wantError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + c, err := NewConfig() + if tc.wantError { + assert.Error(t, err) + } + + assert.Equal(t, c.SecretKey, tc.secretKey) + assert.Equal(t, c.LocalMode, tc.localMode) + assert.Equal(t, c.ConfigSyncInterval, tc.configSyncInterval) + assert.Equal(t, c.IDListSyncInterval, tc.idListSyncInterval) + }) + } +} diff --git a/pkg/statsig/statsig.go b/pkg/statsig/statsig.go new file mode 100644 index 0000000..4d7fe83 --- /dev/null +++ b/pkg/statsig/statsig.go @@ -0,0 +1,37 @@ +package statsig + +import ( + statsig "github.com/statsig-io/go-sdk" +) + +func Initialize(c *Config) { + opts := &statsig.Options{ + Environment: statsig.Environment{Tier: c.environment}, + } + + if c.LocalMode { + opts.LocalMode = true + } + + if c.ConfigSyncInterval > 0 { + opts.ConfigSyncInterval = c.ConfigSyncInterval + } + + if c.IDListSyncInterval > 0 { + opts.IDListSyncInterval = c.IDListSyncInterval + } + + statsig.InitializeWithOptions(c.SecretKey, opts) +} + +func GetExperiment(user statsig.User, experiment string) statsig.DynamicConfig { + return statsig.GetExperiment(user, experiment) +} + +func GetFeatureFlag(user statsig.User, flag string) statsig.FeatureGate { + return statsig.GetGate(user, flag) +} + +func Shutdown() { + statsig.Shutdown() +}