diff --git a/cmd/start.go b/cmd/start.go index 376ebd814..494f152b7 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -40,7 +40,7 @@ func init() { flags.StringP( syncProviderFlagName, "y", "filepath", "Set a sync provider e.g. filepath or remote", ) - flags.StringP(evaluatorFlagName, "e", "json", "Set an evaluator e.g. json") + flags.StringP(evaluatorFlagName, "e", "json", "Set an evaluator e.g. json, yaml/yml") flags.StringP(serverCertPathFlagName, "c", "", "Server side tls certificate path") flags.StringP(serverKeyPathFlagName, "k", "", "Server side tls key path") flags.StringToStringP(providerArgsFlagName, diff --git a/config/samples/example_flags.yaml b/config/samples/example_flags.yaml new file mode 100644 index 000000000..dac0f4586 --- /dev/null +++ b/config/samples/example_flags.yaml @@ -0,0 +1,88 @@ +flags: + myBoolFlag: + state: ENABLED + variants: + 'on': true + 'off': false + defaultVariant: 'on' + myStringFlag: + state: ENABLED + variants: + key1: val1 + key2: val2 + defaultVariant: key1 + myFloatFlag: + state: ENABLED + variants: + one: 1.23 + two: 2.34 + defaultVariant: one + myIntFlag: + state: ENABLED + variants: + one: 1 + two: 2 + defaultVariant: one + myObjectFlag: + state: ENABLED + variants: + object1: + key: val + object2: + key: true + defaultVariant: object1 + isColorYellow: + state: ENABLED + variants: + 'on': true + 'off': false + defaultVariant: 'off' + targeting: + if: + - "==": + - var: + - color + - yellow + - 'on' + - 'off' + fibAlgo: + variants: + recursive: recursive + memo: memo + loop: loop + binet: binet + defaultVariant: recursive + state: ENABLED + targeting: + if: + - "$ref": emailWithFaas + - binet + - null + headerColor: + variants: + red: "#FF0000" + blue: "#0000FF" + green: "#00FF00" + yellow: "#FFFF00" + defaultVariant: red + state: ENABLED + targeting: + if: + - "$ref": emailWithFaas + - fractionalEvaluation: + - email + - - red + - 25 + - - blue + - 25 + - - green + - 25 + - - yellow + - 25 + - null +"$evaluators": + emailWithFaas: + in: + - "@faas.com" + - var: + - email \ No newline at end of file diff --git a/go.mod b/go.mod index 49d8bd28e..60fa2e465 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 github.com/go-chi/chi/v5 v5.0.7 github.com/golang/mock v1.6.0 + github.com/json-iterator/go v1.1.12 github.com/mattn/go-colorable v0.1.12 github.com/open-feature/open-feature-operator v0.0.10-0.20220826061622-a6421d66936a github.com/open-feature/schemas v0.0.0-20220809125333-185e3bd77775 @@ -28,6 +29,7 @@ require ( golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 google.golang.org/grpc v1.49.0 google.golang.org/protobuf v1.28.1 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.25.2 k8s.io/apimachinery v0.25.2 k8s.io/client-go v0.25.2 @@ -55,7 +57,6 @@ require ( github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -92,7 +93,6 @@ require ( google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.80.1 // indirect k8s.io/kube-openapi v0.0.0-20220928191237-829ce0c27909 // indirect diff --git a/pkg/runtime/from_config.go b/pkg/runtime/from_config.go index cd4cc6e93..88febed5a 100644 --- a/pkg/runtime/from_config.go +++ b/pkg/runtime/from_config.go @@ -49,6 +49,10 @@ func (r *Runtime) setService(logger *logger.Logger) { func (r *Runtime) setEvaluatorFromConfig(logger *logger.Logger) error { switch r.config.Evaluator { + case "yaml": + fallthrough + case "yml": + fallthrough case "json": r.Evaluator = eval.NewJSONEvaluator(logger) default: @@ -71,6 +75,8 @@ func (r *Runtime) setSyncImplFromConfig(logger *logger.Logger) error { zap.String("sync", "filepath"), ), ProviderArgs: r.config.ProviderArgs, + // evaluator here is file type: `json`, `yaml` etc., + FileType: r.config.Evaluator, }) rtLogger.Debug(fmt.Sprintf("Using %s sync-provider on %q", r.config.SyncProvider, u)) } diff --git a/pkg/sync/filepath_sync.go b/pkg/sync/filepath_sync.go index e3da3910d..c0192218e 100644 --- a/pkg/sync/filepath_sync.go +++ b/pkg/sync/filepath_sync.go @@ -6,6 +6,9 @@ import ( "fmt" "os" + jsoniter "github.com/json-iterator/go" + "gopkg.in/yaml.v2" + "github.com/fsnotify/fsnotify" "github.com/open-feature/flagd/pkg/logger" ) @@ -14,6 +17,9 @@ type FilePathSync struct { URI string Logger *logger.Logger ProviderArgs ProviderArgs + // FileType indicates the file type e.g., json, yaml/yml etc., + // TODO: limit this to `json` or `yaml only?` + FileType string } func (fs *FilePathSync) Source() string { @@ -28,7 +34,17 @@ func (fs *FilePathSync) Fetch(_ context.Context) (string, error) { if err != nil { return "", err } - return string(rawFile), nil + + switch fs.FileType { + case "yaml": + fallthrough + case "yml": + return yamlToJson(rawFile) + case "json": + return string(rawFile), nil + default: + return "", fmt.Errorf("filepath extension '%v' is not supported", fs.FileType) + } } func (fs *FilePathSync) Notify(ctx context.Context, w chan<- INotify) { @@ -90,3 +106,25 @@ func (fs *FilePathSync) Notify(ctx context.Context, w chan<- INotify) { w <- &Notifier{Event: Event[DefaultEventType]{DefaultEventTypeReady}} // signal readiness to the caller <-ctx.Done() } + +// yamlToJson is a generic helper function to convert +// yaml to json +func yamlToJson(rawFile []byte) (string, error) { + var ms map[string]interface{} + if err := yaml.Unmarshal(rawFile, &ms); err != nil { + return "", fmt.Errorf("unmarshal yaml: %w", err) + } + + // Using jsoniter library here because json.Marshal doesn't understand keys of + // interface{} type (it can only understand string keys) + // More info:https://stackoverflow.com/q/35377477/6874596 + var jsonit = jsoniter.ConfigCompatibleWithStandardLibrary + // Adding spaces here because our evaluator transposer function + // doesn't understand json without indentations quite well + r, err := jsonit.MarshalIndent(ms, "", " ") + if err != nil { + return "", fmt.Errorf("convert yaml to json: %w", err) + } + + return string(r), err +}