From 867ec3e6ce49568b549f17e1ebac8c5c9ffca96b Mon Sep 17 00:00:00 2001 From: Sergey Passichenko Date: Thu, 19 Oct 2023 14:58:22 -0700 Subject: [PATCH] support config metadata --- pkg/star/feature.go | 18 ++++++++++ pkg/star/static/traverse.go | 17 ++++++++++ pkg/star/static/walk.go | 42 ++++++++++++++++++++++-- proto/lekko/feature/v1beta1/static.proto | 2 ++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/pkg/star/feature.go b/pkg/star/feature.go index 1509ef25..eef59491 100644 --- a/pkg/star/feature.go +++ b/pkg/star/feature.go @@ -39,6 +39,7 @@ const ( ResultVariableName string = "result" DefaultValueAttrName string = "default" DescriptionAttrName string = "description" + MetadataAttrName string = "metadata" // TODO: Fully migrate to overrides over rules RulesAttrName string = "rules" OverridesAttrName string = "overrides" @@ -54,6 +55,7 @@ var ( OverridesAttrName: {}, validatorAttrName: {}, unitTestsAttrName: {}, + MetadataAttrName: {}, } ) @@ -295,6 +297,22 @@ func (fb *featureBuilder) getDescription(featureVal *starlarkstruct.Struct) (str return dsc.GoString(), nil } +func (fb *featureBuilder) getMetadata(featureVal *starlarkstruct.Struct) (*structpb.Value, error) { + metadataVal, err := featureVal.Attr(MetadataAttrName) + if err != nil { + return nil, errors.Wrap(err, "metadata attribute") + } + metadataDict, ok := metadataVal.(*starlark.Dict) + if !ok { + return nil, fmt.Errorf("metadata must be a dict (got a %s)", metadataVal.Type()) + } + metadataMap, err := translateContext(metadataDict) + if err != nil { + return nil, errors.Wrap(err, "translate metadata attribute") + } + return structpb.NewValue(metadataMap) +} + func (fb *featureBuilder) addOverrides(f *feature.Feature, featureVal *starlarkstruct.Struct) ([]starlark.Value, error) { overridesVal, err := featureVal.Attr(OverridesAttrName) if err != nil { diff --git a/pkg/star/static/traverse.go b/pkg/star/static/traverse.go index acf21a82..406f5589 100644 --- a/pkg/star/static/traverse.go +++ b/pkg/star/static/traverse.go @@ -30,6 +30,7 @@ const ( ResultVariableName string = "result" DefaultValueAttrName string = "default" DescriptionAttrName string = "description" + MetadataAttrName string = "metadata" // TODO: Fully migrate to overrides over rules RulesAttrName string = "rules" OverridesAttrName string = "overrides" @@ -40,11 +41,13 @@ func defaultNoop(v *build.Expr) error { return nil } func descriptionNoop(v *build.StringExpr) error { return nil } func rulesNoop(rules *overridesWrapper) error { return nil } func importsNoop(imports *importsWrapper) error { return nil } +func metadataNoop(ast *starFeatureAST) error { return nil} type defaultFn func(v *build.Expr) error type descriptionFn func(v *build.StringExpr) error type overridesFn func(rules *overridesWrapper) error type importsFn func(imports *importsWrapper) error +type metadataFn func(ast *starFeatureAST) error // Traverses a lekko starlark file, running methods on various // components of the file. Methods can be provided to read the @@ -57,6 +60,7 @@ type traverser struct { descriptionFn descriptionFn overridesFn overridesFn protoImportsFn importsFn + metadataFn metadataFn } func newTraverser(f *build.File, nv feature.NamespaceVersion) *traverser { @@ -67,6 +71,7 @@ func newTraverser(f *build.File, nv feature.NamespaceVersion) *traverser { descriptionFn: descriptionNoop, overridesFn: rulesNoop, protoImportsFn: importsNoop, + metadataFn: metadataNoop, } } @@ -90,6 +95,11 @@ func (t *traverser) withProtoImportsFn(fn importsFn) *traverser { return t } +func (t *traverser) withMetadataFn(fn metadataFn) *traverser { + t.metadataFn = fn + return t +} + func (t *traverser) traverse() error { imports := t.getProtoImports() if err := t.protoImportsFn(imports); err != nil { @@ -118,6 +128,9 @@ func (t *traverser) traverse() error { if err := t.descriptionFn(descriptionStr); err != nil { return errors.Wrap(err, "description fn") } + if err := t.metadataFn(ast); err != nil { + return errors.Wrap(err, "metadata fn") + } // rules if err := ast.parseOverrides(t.overridesFn, t.nv); err != nil { return err @@ -326,6 +339,10 @@ type importVal struct { assignExpr *build.AssignExpr } +type metadataWrapper struct { + metadataExpr *build.DictExpr +} + func newOverride(li build.Expr) (*override, error) { tupleV, ok := li.(*build.TupleExpr) if !ok { diff --git a/pkg/star/static/walk.go b/pkg/star/static/walk.go index f355e6fd..1b5587cf 100644 --- a/pkg/star/static/walk.go +++ b/pkg/star/static/walk.go @@ -85,7 +85,8 @@ func (w *walker) Build() (*featurev1beta1.StaticFeature, error) { withDefaultFn(w.buildDefaultFn(ret)). withDescriptionFn(w.buildDescriptionFn(ret)). withOverridesFn(w.buildRulesFn(ret)). - withProtoImportsFn(w.buildProtoImportsFn(ret)) + withProtoImportsFn(w.buildProtoImportsFn(ret)). + withMetadataFn(w.buildMetadataFn(ret)) if err := t.traverse(); err != nil { return nil, errors.Wrap(err, "traverse") @@ -101,7 +102,8 @@ func (w *walker) Mutate(f *featurev1beta1.StaticFeature) ([]byte, error) { t := newTraverser(ast, w.nv). withDefaultFn(w.mutateDefaultFn(f)). withDescriptionFn(w.mutateDescriptionFn(f)). - withOverridesFn(w.mutateOverridesFn(f)) + withOverridesFn(w.mutateOverridesFn(f)). + withMetadataFn(w.mutateMetadataFn(f)) if err := t.traverse(); err != nil { return nil, errors.Wrap(err, "traverse") @@ -240,6 +242,27 @@ func (w *walker) buildDescriptionFn(f *featurev1beta1.StaticFeature) description } } +func (w *walker) buildMetadataFn(f *featurev1beta1.StaticFeature) metadataFn { + return func(ast *starFeatureAST) error { + metadataExprPtr, found := ast.get(MetadataAttrName) + if !found { + return nil + } + metadataExpr := *metadataExprPtr + metadataDict, ok := metadataExpr.(*build.DictExpr) + if !ok { + return errors.Wrapf(ErrUnsupportedStaticParsing, "metadata kwarg: expected dict, got %T", metadataExpr) + } + metadataValue, err := w.extractJSONValue(metadataDict) + if err != nil { + return errors.Wrap(err, "extract metadata") + } + f.Feature.Metadata = metadataValue + f.FeatureOld.Metadata = metadataValue + return nil + } +} + func (w *walker) buildRulesFn(f *featurev1beta1.StaticFeature) overridesFn { return func(overridesW *overridesWrapper) error { for i, o := range overridesW.overrides { @@ -485,6 +508,21 @@ func (w *walker) mutateDescriptionFn(f *featurev1beta1.StaticFeature) descriptio } } +func (w *walker) mutateMetadataFn(f *featurev1beta1.StaticFeature) metadataFn { + return func(ast *starFeatureAST) error { + metadataProto := f.FeatureOld.GetMetadata() + if metadataProto == nil { + return nil + } + metadataStarDict, err := w.genJSONValue(metadataProto, nil) + if err != nil { + return err + } + ast.set(MetadataAttrName, metadataStarDict) + return nil + } +} + func (w *walker) mutateOverridesFn(f *featurev1beta1.StaticFeature) overridesFn { return func(overridesW *overridesWrapper) error { var newOverrides []override diff --git a/proto/lekko/feature/v1beta1/static.proto b/proto/lekko/feature/v1beta1/static.proto index e6a7db68..4e47aa3a 100644 --- a/proto/lekko/feature/v1beta1/static.proto +++ b/proto/lekko/feature/v1beta1/static.proto @@ -16,6 +16,7 @@ syntax = "proto3"; package lekko.feature.v1beta1; +import "google/protobuf/struct.proto"; import "lekko/feature/v1beta1/feature.proto"; // Represents a statically parsed feature. @@ -47,6 +48,7 @@ message FeatureStruct { string description = 2; StarExpr default = 3; Rules rules = 4; + google.protobuf.Value metadata = 7; } message Rules {