Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
vendor/
.idea/
.vscode/
.DS_Store
internal/.DS_Store
local_evaluation.go
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ module github.com/LambdaTest/lambda-featureflag-go-sdk
go 1.20

require (
github.com/amplitude/analytics-go v1.0.1
github.com/joho/godotenv v1.5.1
github.com/sirupsen/logrus v1.9.3
github.com/spaolacci/murmur3 v1.1.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)

require (
github.com/amplitude/analytics-go v1.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
github.com/amplitude/analytics-go v1.0.1 h1:rrdC5VBctlJigSk0kw7ktwSijob/wyH4bop2SqWduCU=
github.com/amplitude/analytics-go v1.0.1/go.mod h1:kAQG8OQ6aPOxZrEZ3+/NFCfxdYSyjqXZhgkjWFD3/vo=
github.com/amplitude/experiment-go-server v1.3.0 h1:zsrbMiaI6yDwNesDREX6Wy83w4kZ0vqJEdxko69DmU8=
github.com/amplitude/experiment-go-server v1.3.0/go.mod h1:5HERnGGohucx2mNr/0fLeQ9xFWNmeMbtzsxH2beB+hs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -16,19 +14,13 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
83 changes: 58 additions & 25 deletions localEvaluation/localEvaluation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ package localEvaluation

import (
"fmt"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment/local"
"github.com/joho/godotenv"

"os"
"strconv"
"time"

"github.com/joho/godotenv"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment/local"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/rootOrg"
)

var (
client *local.Client
LocalEvaluationConfigDebug = true
LocalEvaluationConfigServerUrl = "https://api.lambdatest.com"
LocalEvaluationConfigPollInterval = 120
LocalEvaluationConfigPollerRequestTimeout = 60
LocalEvaluationDeploymentKey = "server-jAqqJaX3l8PgNiJpcv9j20ywPzANQQFh"
rootOrgClient *rootOrg.Client
localEvaluationConfigDebug = true
localEvaluationConfigServerUrl = "https://api.lambdatest.com"
localEvaluationConfigPollInterval = 120
localEvaluationConfigPollerRequestTimeout = 60
localEvaluationDeploymentKey = "server-jAqqJaX3l8PgNiJpcv9j20ywPzANQQFh"
retries = 5
)

Expand All @@ -43,7 +45,7 @@ type UserProperties struct {
TemplateId string `json:"template_id,omitempty"`
}

func Init() {
func initVars() {
err := godotenv.Load()
if err != nil {
fmt.Printf("No .env file found")
Expand All @@ -52,31 +54,31 @@ func Init() {
}

if os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG") != "" {
LocalEvaluationConfigDebug, _ = strconv.ParseBool(os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG"))
localEvaluationConfigDebug, _ = strconv.ParseBool(os.Getenv("LOCAL_EVALUATION_CONFIG_DEBUG"))
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL") != "" {
LocalEvaluationConfigServerUrl = os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL")
localEvaluationConfigServerUrl = os.Getenv("LOCAL_EVALUATION_CONFIG_SERVER_URL")
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL") != "" {
LocalEvaluationConfigPollInterval, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL"))
localEvaluationConfigPollInterval, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLL_INTERVAL"))
}
if os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT") != "" {
LocalEvaluationConfigPollerRequestTimeout, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT"))
localEvaluationConfigPollerRequestTimeout, _ = strconv.Atoi(os.Getenv("LOCAL_EVALUATION_CONFIG_POLLER_REQUEST_TIMEOUT"))
}
if os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY") != "" {
LocalEvaluationDeploymentKey = os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY")
localEvaluationDeploymentKey = os.Getenv("LOCAL_EVALUATION_DEPLOYMENT_KEY")
}
}

func Initialize() {
Init()
initVars()
config := local.Config{
Debug: LocalEvaluationConfigDebug,
ServerUrl: LocalEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(LocalEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(LocalEvaluationConfigPollerRequestTimeout) * time.Second,
Debug: localEvaluationConfigDebug,
ServerUrl: localEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(localEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(localEvaluationConfigPollerRequestTimeout) * time.Second,
}
client = local.Initialize(LocalEvaluationDeploymentKey, &config)
client = local.Initialize(localEvaluationDeploymentKey, &config)
var err error
for i := 0; i < retries; i++ {
err = client.Start()
Expand Down Expand Up @@ -111,8 +113,31 @@ func InitializeWithConfig(conf local.Config, deploymentKey string) {
}
}

func InitializeRootOrg() {
initVars()
rootOrgClient = rootOrg.NewClient(localEvaluationDeploymentKey, &rootOrg.Config{
ServerUrl: localEvaluationConfigServerUrl,
FlagConfigPollerInterval: time.Duration(localEvaluationConfigPollInterval) * time.Second,
FlagConfigPollerRequestTimeout: time.Duration(localEvaluationConfigPollerRequestTimeout) * time.Second,
})
var err error
for i := 0; i < retries; i++ {
err = rootOrgClient.Start()
if err != nil {
err = fmt.Errorf("unable to get root orgs with given config %+v attempt:%v with error %s", rootOrgClient.Config, i+1, err.Error())
continue
} else {
break
}
}
if err != nil {
err = fmt.Errorf("unable to get root orgs with given config %+v with error %s", rootOrgClient.Config, err.Error())
panic(err)
}
}

func fetch(user UserProperties) (map[string]experiment.Variant, error) {
userProp := map[string]interface{}{
userProp := map[string]any{
"org_id": user.OrgId,
"org_name": user.OrgName,
"username": user.Username,
Expand All @@ -128,6 +153,14 @@ func fetch(user UserProperties) (map[string]experiment.Variant, error) {
UserProperties: userProp,
}

// Evaluate root org to get the parent org id
if expUser.UserProperties["org_id"] != "" && rootOrgClient != nil {
rootOrg, ok := rootOrgClient.Evaluate(expUser.UserProperties["org_id"])
if ok {
expUser.UserProperties["org_id"] = rootOrg
}
}

result, err := client.EvaluateV2(&expUser, []string{})
if err != nil {
return nil, err
Expand All @@ -137,7 +170,7 @@ func fetch(user UserProperties) (map[string]experiment.Variant, error) {

func getValue(flagName string, user UserProperties) Variant {
result, _ := fetch(user)
if result != nil && len(result) != 0 {
if len(result) != 0 {
if value, ok := result[flagName]; ok {
return Variant{
Key: value.Key,
Expand All @@ -151,7 +184,7 @@ func getValue(flagName string, user UserProperties) Variant {
func getMapOfValue(user UserProperties) map[string]interface{} {
flags := make(map[string]interface{})
result, _ := fetch(user)
if result != nil && len(result) != 0 {
if len(result) != 0 {
for k, v := range result {
if v.Value != "" {
flags[k] = v.Value
Expand Down
18 changes: 18 additions & 0 deletions model/lums.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package model

type OrgId int64

type OrgMap map[OrgId]OrgId

// ErrorResponse represents the error response from the LUMS API
type ErrorResponse struct {
Type string `json:"type"`
Title string `json:"title"`
Message string `json:"message"`
}

// SuccessResponse represents the success response from the LUMS API
type SuccessResponse struct {
Type string `json:"type"`
Data OrgMap `json:"data"`
}
7 changes: 3 additions & 4 deletions pkg/experiment/local/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/amplitude/analytics-go/amplitude"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"sync"

"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/evaluation"

"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
"github.com/amplitude/analytics-go/amplitude"

"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/evaluation"
"github.com/LambdaTest/lambda-featureflag-go-sdk/internal/logger"
"github.com/LambdaTest/lambda-featureflag-go-sdk/pkg/experiment"
)

var clients = map[string]*Client{}
Expand Down
59 changes: 59 additions & 0 deletions pkg/rootOrg/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package rootOrg

import (
"strconv"
"sync"

"github.com/LambdaTest/lambda-featureflag-go-sdk/model"
)

type Client struct {
apiKey string
Config *Config
poller *poller
flags *model.OrgMap
flagsMutex *sync.RWMutex
}

func NewClient(apiKey string, config *Config) *Client {
return &Client{
apiKey: apiKey,
Config: config,
flagsMutex: &sync.RWMutex{},
flags: &model.OrgMap{},
poller: newPoller(),
}
}

func (c *Client) Start() error {
c.poller = newPoller()
c.poller.Poll(c.Config.FlagConfigPollerInterval, func() {
c.pollFlags()
})
return c.pollFlags()
}

func (c *Client) Stop() {
close(c.poller.shutdown)
}

func (c *Client) pollFlags() error {
c.flagsMutex.Lock()
defer c.flagsMutex.Unlock()
rootOrgs, err := GetRootOrgs(c.apiKey, c.Config.ServerUrl, c.Config.FlagConfigPollerRequestTimeout)
if err != nil {
return err
}
c.flags = rootOrgs
return nil
}

func (c *Client) Evaluate(orgId any) (any, bool) {
c.flagsMutex.RLock()
defer c.flagsMutex.RUnlock()

orgIdStr, _ := orgId.(string)
orgIdInt, _ := strconv.ParseInt(orgIdStr, 10, 64)
org, ok := (*c.flags)[model.OrgId(orgIdInt)]
return org, ok
}
17 changes: 17 additions & 0 deletions pkg/rootOrg/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package rootOrg

import (
"time"
)

type Config struct {
ServerUrl string
FlagConfigPollerInterval time.Duration
FlagConfigPollerRequestTimeout time.Duration
}

var DefaultConfig = &Config{
ServerUrl: "https://api.lambdatest.com",
FlagConfigPollerInterval: 120 * time.Second,
FlagConfigPollerRequestTimeout: 60 * time.Second,
}
28 changes: 28 additions & 0 deletions pkg/rootOrg/poller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package rootOrg

import "time"

type poller struct {
shutdown chan bool
}

func newPoller() *poller {
return &poller{
shutdown: make(chan bool),
}
}

func (p *poller) Poll(interval time.Duration, function func()) {
ticker := time.NewTicker(interval)
go func() {
for {
select {
case <-p.shutdown:
ticker.Stop()
return
case <-ticker.C:
go function()
}
}
}()
}
Loading