Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion infra/sidecar.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ----- Builder image
ARG GOLANG_VERSION=1.23.6
ARG GOLANG_VERSION=1.24.0
FROM golang:${GOLANG_VERSION}-bookworm AS builder

ARG FIPS_MODE
Expand Down
4 changes: 4 additions & 0 deletions splitd.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ sdk:
apikey: <server-side-apitoken>
labelsEnabled: true
streamingEnabled: true
fallbackTreatment:
global_fallback_treatment:
treatment: other
by_flag_fallback_treatment: {}
urls:
auth: https://auth.split.io
sdk: https://sdk.split.io/api
Expand Down
2 changes: 1 addition & 1 deletion splitio/commitsha.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package splitio

const CommitSHA = "a651b23"
const CommitSHA = "085f07b"
61 changes: 36 additions & 25 deletions splitio/conf/splitcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ type CliArgs struct {
WriteTimeoutMS int

// command
Method string
Key string
BucketingKey string
Feature string
Features []string
TrafficType string
EventType string
EventVal *float64
Attributes map[string]interface{}
Method string
Key string
BucketingKey string
Feature string
Features []string
TrafficType string
EventType string
EventVal *float64
Attributes map[string]interface{}
ImpressionProperties map[string]interface{}
}

func (a *CliArgs) LinkOpts() (*link.ConsumerOptions, error) {
Expand Down Expand Up @@ -85,6 +86,7 @@ func ParseCliArgs() (*CliArgs, error) {
et := cliFlags.String("event-type", "", "event type")
ev := cliFlags.String("value", "", "event associated value")
at := cliFlags.String("attributes", "", "json representation of attributes")
pr := cliFlags.String("impression-properties", "", "json representation of")
err := cliFlags.Parse(os.Args[1:])
if err != nil {
return nil, fmt.Errorf("error parsing arguments: %w", err)
Expand All @@ -107,22 +109,31 @@ func ParseCliArgs() (*CliArgs, error) {
return nil, fmt.Errorf("error parsing attributes: %w", err)
}

if *pr == "" {
*pr = "null"
}
impressionPorperties := make(map[string]interface{})
if err = json.Unmarshal([]byte(*pr), &impressionPorperties); err != nil {
return nil, fmt.Errorf("error parsing impression properties: %w", err)
}

return &CliArgs{
ID: *id,
Serialization: *s,
Protocol: *p,
LogLevel: *ll,
ConnType: *ct,
ConnAddr: *ca,
BufSize: *bs,
Method: *m,
Key: *k,
BucketingKey: *bk,
Feature: *f,
Features: strings.Split(*fs, ","),
TrafficType: *tt,
EventType: *et,
EventVal: eventVal,
Attributes: attrs,
ID: *id,
Serialization: *s,
Protocol: *p,
LogLevel: *ll,
ConnType: *ct,
ConnAddr: *ca,
BufSize: *bs,
Method: *m,
Key: *k,
BucketingKey: *bk,
Feature: *f,
Features: strings.Split(*fs, ","),
TrafficType: *tt,
EventType: *et,
EventVal: eventVal,
Attributes: attrs,
ImpressionProperties: impressionPorperties,
}, nil
}
2 changes: 2 additions & 0 deletions splitio/conf/splitcli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestCliConfig(t *testing.T) {
"-event-type=someEventType",
"-value=0.123",
`-attributes={"some": "attribute"}`,
`-impression-properties={"userId": "123", "age": 30, "premium": true, "balance": 99.5}`,
}

parsed, err := ParseCliArgs()
Expand All @@ -42,6 +43,7 @@ func TestCliConfig(t *testing.T) {
assert.Equal(t, "someEventType", parsed.EventType)
assert.Equal(t, lang.Ref(float64(0.123)), parsed.EventVal)
assert.Equal(t, map[string]interface{}{"some": "attribute"}, parsed.Attributes)
assert.Equal(t, map[string]interface{}{"userId": "123", "age": float64(30), "premium": true, "balance": 99.5}, parsed.ImpressionProperties)

// test bad buffer size
os.Args = []string{os.Args[0], "-buffer-size=sarasa"}
Expand Down
131 changes: 123 additions & 8 deletions splitio/conf/splitd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/splitio/go-split-commons/v9/dtos"
"github.com/splitio/go-toolkit/v5/logging"
"github.com/splitio/splitd/splitio/common/lang"
"github.com/splitio/splitd/splitio/link"
Expand Down Expand Up @@ -122,21 +123,23 @@ func (l *Link) ToListenerOpts() (*link.ListenerOptions, error) {
}

type SDK struct {
Apikey string `yaml:"apikey"`
LabelsEnabled *bool `yaml:"labelsEnabled"`
StreamingEnabled *bool `yaml:"streamingEnabled"`
URLs URLs `yaml:"urls"`
FeatureFlags FeatureFlags `yaml:"featureFlags"`
Impressions Impressions `yaml:"impressions"`
Events Events `yaml:"events"`
FlagSetsFilter []string `yaml:"flagSetsFilter"`
Apikey string `yaml:"apikey"`
LabelsEnabled *bool `yaml:"labelsEnabled"`
StreamingEnabled *bool `yaml:"streamingEnabled"`
FallbackTreatment fallbackTreatmentInput `yaml:"fallbackTreatment"`
URLs URLs `yaml:"urls"`
FeatureFlags FeatureFlags `yaml:"featureFlags"`
Impressions Impressions `yaml:"impressions"`
Events Events `yaml:"events"`
FlagSetsFilter []string `yaml:"flagSetsFilter"`
}

func (s *SDK) PopulateWithDefaults() {
cfg := sdkConf.DefaultConfig()
s.Apikey = apikeyPlaceHolder
s.LabelsEnabled = lang.Ref(cfg.LabelsEnabled)
s.StreamingEnabled = lang.Ref(cfg.StreamingEnabled)
s.FallbackTreatment = fallbackTreatmentFromConfig(cfg.FallbackTreatment)
s.URLs.PopulateWithDefaults()
s.FeatureFlags.PopulateWithDefaults()
s.Impressions.PopulateWithDefaults()
Expand Down Expand Up @@ -216,6 +219,11 @@ func (s *SDK) ToSDKConf() *sdkConf.Config {
if len(s.FlagSetsFilter) > 0 {
cfg.FlagSetsFilter = s.FlagSetsFilter
}
if parsed, err := (&s.FallbackTreatment).toConfig(); err != nil {
log.Printf("[splitd] fallbackTreatment: %v", err)
} else if parsed != nil {
cfg.FallbackTreatment = *parsed
}
return cfg
}

Expand Down Expand Up @@ -310,6 +318,113 @@ func (p *Profiling) PopulateWithDefaults() {
p.Port = 8888
}

// fallbackTreatmentFromConfig maps the SDK default config's FallbackTreatment into our input type.
func fallbackTreatmentFromConfig(c dtos.FallbackTreatmentConfig) fallbackTreatmentInput {
parsed := new(dtos.FallbackTreatmentConfig)
*parsed = c
return fallbackTreatmentInput{parsed: parsed}
}

type fallbackTreatmentEntry struct {
Treatment *string `json:"treatment" yaml:"treatment"`
Config *string `json:"config,omitempty" yaml:"config,omitempty"`
}

type fallbackTreatmentInput struct {
parsed *dtos.FallbackTreatmentConfig
raw string
}

func (f *fallbackTreatmentInput) UnmarshalYAML(value *yaml.Node) error {
if value == nil {
return nil
}
switch value.Kind {
case yaml.ScalarNode:
var s string
if err := value.Decode(&s); err != nil {
return err
}
f.parsed = nil
f.raw = strings.TrimSpace(s)
return nil
case yaml.MappingNode:
var m struct {
Global *fallbackTreatmentEntry `yaml:"global_fallback_treatment"`
ByFlag map[string]fallbackTreatmentEntry `yaml:"by_flag_fallback_treatment"`
}
if err := value.Decode(&m); err != nil {
return err
}
out := dtos.FallbackTreatmentConfig{}
if m.Global != nil && m.Global.Treatment != nil {
out.GlobalFallbackTreatment = &dtos.FallbackTreatment{
Treatment: m.Global.Treatment,
Config: m.Global.Config,
}
}
if len(m.ByFlag) > 0 {
out.ByFlagFallbackTreatment = make(map[string]dtos.FallbackTreatment)
for name, v := range m.ByFlag {
if v.Treatment != nil {
out.ByFlagFallbackTreatment[name] = dtos.FallbackTreatment{
Treatment: v.Treatment,
Config: v.Config,
}
}
}
}
f.parsed = &out
return nil
}
return nil
}

func (f *fallbackTreatmentInput) toConfig() (*dtos.FallbackTreatmentConfig, error) {
if f == nil {
return nil, nil
}
if f.raw != "" {
return parseFallbackTreatmentJSON(f.raw)
}
if f.parsed != nil {
return f.parsed, nil
}
return nil, nil
}

func parseFallbackTreatmentJSON(raw string) (*dtos.FallbackTreatmentConfig, error) {
var wrapper struct {
FallbackTreatment struct {
GlobalFallbackTreatment *fallbackTreatmentEntry `json:"global_fallback_treatment"`
ByFlagFallbackTreatment map[string]fallbackTreatmentEntry `json:"by_flag_fallback_treatment"`
} `json:"fallback_treatment"`
}
if err := json.Unmarshal([]byte(raw), &wrapper); err != nil {
return nil, fmt.Errorf("invalid JSON: %w", err)
}
out := dtos.FallbackTreatmentConfig{}
inner := &wrapper.FallbackTreatment
if inner.GlobalFallbackTreatment != nil && inner.GlobalFallbackTreatment.Treatment != nil {
out.GlobalFallbackTreatment = &dtos.FallbackTreatment{
Treatment: inner.GlobalFallbackTreatment.Treatment,
Config: inner.GlobalFallbackTreatment.Config,
}
}
if len(inner.ByFlagFallbackTreatment) > 0 {
out.ByFlagFallbackTreatment = make(map[string]dtos.FallbackTreatment)
for name, v := range inner.ByFlagFallbackTreatment {
if v.Treatment != nil {
out.ByFlagFallbackTreatment[name] = dtos.FallbackTreatment{
Treatment: v.Treatment,
Config: v.Config,
}
}
}
}
return &out, nil
}

func ReadConfig() (*Config, error) {
cfgFN := defaultConfigFN
if fromEnv := os.Getenv("SPLITD_CONF_FILE"); fromEnv != "" {
Expand Down
44 changes: 44 additions & 0 deletions splitio/conf/splitd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/splitio/splitd/splitio/link/transfer"
"github.com/splitio/splitd/splitio/sdk/conf"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func TestConfig(t *testing.T) {
Expand All @@ -30,6 +32,7 @@ func TestConfig(t *testing.T) {
cfg = Config{}

assert.Nil(t, cfg.parse(dir+string(filepath.Separator)+"splitd.yaml.tpl"))
expected.SDK.FallbackTreatment = cfg.SDK.FallbackTreatment
assert.Equal(t, expected, cfg)

assert.Error(t, cfg.parse("someNonexistantFile"))
Expand Down Expand Up @@ -185,3 +188,44 @@ func TestDefaultConf(t *testing.T) {
assert.Equal(t, defaultLogLevel, *c.Logger.Level)
assert.Equal(t, defaultLogOutput, *c.Logger.Output)
}

func TestFallbackTreatmentToSDKConf(t *testing.T) {
// JSON string form
var cfg Config
cfg.PopulateWithDefaults()
err := yaml.Unmarshal([]byte(`
sdk:
apikey: test
fallbackTreatment: '{"fallback_treatment":{"global_fallback_treatment":{"treatment":"control"},"by_flag_fallback_treatment":{"my_flag":{"treatment":"off"}}}}'
`), &cfg)
assert.Nil(t, err)
sdkConf := cfg.SDK.ToSDKConf()
require.NotNil(t, sdkConf)
require.NotNil(t, sdkConf.FallbackTreatment.GlobalFallbackTreatment)
require.NotEmpty(t, sdkConf.FallbackTreatment.ByFlagFallbackTreatment)
assert.Equal(t, "control", *sdkConf.FallbackTreatment.GlobalFallbackTreatment.Treatment)
assert.Equal(t, "off", *sdkConf.FallbackTreatment.ByFlagFallbackTreatment["my_flag"].Treatment)

// Native YAML object form
var cfg2 Config
cfg2.PopulateWithDefaults()
err = yaml.Unmarshal([]byte(`
sdk:
apikey: test
fallbackTreatment:
global_fallback_treatment:
treatment: global_val
by_flag_fallback_treatment:
some_flag:
treatment: on
config: "{}"
`), &cfg2)
assert.Nil(t, err)
sdkConf2 := cfg2.SDK.ToSDKConf()
require.NotNil(t, sdkConf2)
require.NotNil(t, sdkConf2.FallbackTreatment.GlobalFallbackTreatment)
require.Contains(t, sdkConf2.FallbackTreatment.ByFlagFallbackTreatment, "some_flag")
assert.Equal(t, "global_val", *sdkConf2.FallbackTreatment.GlobalFallbackTreatment.Treatment)
assert.Equal(t, "on", *sdkConf2.FallbackTreatment.ByFlagFallbackTreatment["some_flag"].Treatment)
assert.Equal(t, "{}", *sdkConf2.FallbackTreatment.ByFlagFallbackTreatment["some_flag"].Config)
}
14 changes: 10 additions & 4 deletions splitio/link/client/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import (
)

type ClientInterface interface {
Treatment(key string, bucketingKey string, feature string, attrs map[string]interface{}) (*Result, error)
Treatments(key string, bucketingKey string, features []string, attrs map[string]interface{}) (Results, error)
TreatmentWithConfig(key string, bucketingKey string, feature string, attrs map[string]interface{}) (*Result, error)
TreatmentsWithConfig(key string, bucketingKey string, features []string, attrs map[string]interface{}) (Results, error)
Treatment(key string, bucketingKey string, feature string, attrs map[string]interface{}, optFns ...OptFn) (*Result, error)
Treatments(key string, bucketingKey string, features []string, attrs map[string]interface{}, optFns ...OptFn) (Results, error)
TreatmentWithConfig(key string, bucketingKey string, feature string, attrs map[string]interface{}, optFns ...OptFn) (*Result, error)
TreatmentsWithConfig(key string, bucketingKey string, features []string, attrs map[string]interface{}, optFns ...OptFn) (Results, error)
Track(key string, trafficType string, eventType string, value *float64, properties map[string]interface{}) error
SplitNames() ([]string, error)
Split(name string) (*sdk.SplitView, error)
Expand All @@ -24,3 +24,9 @@ type Result struct {
}

type Results = map[string]Result

type Options struct {
EvaluationOptions *dtos.EvaluationOptions
}

type OptFn = func(o *Options)
Loading