-
Notifications
You must be signed in to change notification settings - Fork 88
feat: add flagmap config support #1790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
✅ Deploy Preview for polite-licorice-3db33c canceled.
|
raw, err := txn.First(flagsTable, keySourceCompoundIndex, key, source) | ||
if err != nil { | ||
s.logger.Error(fmt.Sprintf("unable to get flag %s from source %s: %v", key, source, err)) | ||
continue | ||
} | ||
oldFlag, ok := raw.(model.Flag) | ||
// If we already have a flag with the same key and source, we need to check if it has the same flagSetId | ||
if ok { | ||
if oldFlag.FlagSetId != newFlag.FlagSetId { | ||
// If the flagSetId is different, we need to delete the entry, since flagSetId+key represents the primary index, and it's now been changed. | ||
// This is important especially for clients listening to flagSetId changes, as they expect the flag to be removed from the set in this case. | ||
_, err = txn.DeleteAll(flagsTable, idIndex, oldFlag.FlagSetId, key) | ||
if err != nil { | ||
s.logger.Error(fmt.Sprintf("unable to delete flags with key %s and flagSetId %s: %v", key, oldFlag.FlagSetId, err)) | ||
continue | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am not sure, that we actually need this - we can have multiple sources with the same key, but also different flagsetids - so if a flag was deleted in one source, does not mean we need to delete it all - or am i missing here something
2348324
to
00afcf4
Compare
a3ac22d
to
699ea48
Compare
Summary of ChangesThis pull request significantly refactors the flag configuration handling by introducing support for an array-based flag definition format. This change required comprehensive updates to the JSON schema validation process, a redesign of the internal flag storage to correctly manage flags with potentially non-unique keys across different flag sets, and a cleaner interface for flag evaluation. The aim is to provide a more flexible and robust system for defining and managing feature flags. Highlights
Changelog
Activity
|
699ea48
to
db7b81e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces support for a new array-based flag configuration format (flagmap
), which is a significant and welcome feature. The implementation correctly handles both the old map-based format and the new array-based one.
My review focuses on a few key areas for improvement:
- Efficiency: The flag definition parsing can be made more efficient by avoiding multiple JSON unmarshalling operations.
- Error Handling: Schema validation for flag configurations should probably return an error instead of just a warning to prevent running with invalid configurations.
- Regressions: The changes to the
IEvaluator.SetState
interface have removed the event notification mechanism, which affects telemetry for flag changes. This seems like a regression that should be addressed. - Code Quality: There are a few minor issues, like an invalid struct tag and a test that could be more robust.
Overall, this is a good start, and with a few refinements, it will be a great addition.
type IEvaluator interface { | ||
GetState() (string, error) | ||
SetState(payload sync.DataSync) (map[string]interface{}, bool, error) | ||
SetState(payload sync.DataSync) error | ||
IResolver | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The signature of SetState
has been changed, and it no longer returns events about flag changes. This has removed the mechanism for notifying about flag creations, updates, and deletions.
This functionality was used for telemetry to track the number of flag changes. While the PR description mentions this is a naive attempt, this seems like a significant regression that should be addressed. The callers of SetState
in the various sync providers will also need to be updated to reflect this new signature.
Signed-off-by: Simon Schrottner <[email protected]> diff --git c/core/go.mod i/core/go.mod index aff41b9..ebb2609 100644 --- c/core/go.mod +++ i/core/go.mod @@ -14,10 +14,11 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-memdb v1.3.5 - github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 + github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83 github.com/open-feature/open-feature-operator/apis v0.2.45 github.com/prometheus/client_golang v1.22.0 github.com/robfig/cron v1.2.0 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/stretchr/testify v1.10.0 github.com/twmb/murmur3 v1.1.8 github.com/xeipuuv/gojsonschema v1.2.0 @@ -113,6 +114,8 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git c/core/go.sum i/core/go.sum index 6cb5c30..3fdfe73 100644 --- c/core/go.sum +++ i/core/go.sum @@ -119,6 +119,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/diegoholiveira/jsonlogic/v3 v3.8.4 h1:IVVU/VLz2hn10ImbmibjiUkdVsSFIB1vfDaOVsaipH4= github.com/diegoholiveira/jsonlogic/v3 v3.8.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk= github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -217,9 +219,14 @@ github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmv github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= +github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -253,8 +260,10 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 h1:r3e+qs3QUdf4+lUi2ZZnSHgYkjeLIb5yu5jo+ypA8iw= -github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U= +github.com/open-feature/flagd-schemas v0.2.9-0.20250827201840-3a4396c00de5 h1:TbyJKxuj174kiOMDqoR1sBcd+DeSnSrOmtT4PNvOGrA= +github.com/open-feature/flagd-schemas v0.2.9-0.20250827201840-3a4396c00de5/go.mod h1:C0jnJ4C3j2LzGuqKgLDdTsdfKEWQp6sOHZyxu3QohFU= +github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83 h1:CxYMSNaDtkudraJUuJHUnGGa+v4ZaaxxjJsmhMKM3QQ= +github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83/go.mod h1:C0jnJ4C3j2LzGuqKgLDdTsdfKEWQp6sOHZyxu3QohFU= github.com/open-feature/open-feature-operator/apis v0.2.45 h1:URnUf22ZoAx7/W8ek8dXCBYgY8FmnFEuEOSDLROQafY= github.com/open-feature/open-feature-operator/apis v0.2.45/go.mod h1:PYh/Hfyna1lZYZUeu/8LM0qh0ZgpH7kKEXRLYaaRhGs= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -281,6 +290,8 @@ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= diff --git c/core/pkg/evaluator/json.go i/core/pkg/evaluator/json.go index 8a0b4fb..2c9b34a 100644 --- c/core/pkg/evaluator/json.go +++ i/core/pkg/evaluator/json.go @@ -6,17 +6,19 @@ import ( "encoding/json" "errors" "fmt" + flagd_definitions "github.com/open-feature/flagd-schemas/json" + "log" "regexp" "strconv" "strings" "time" "github.com/diegoholiveira/jsonlogic/v3" - schema "github.com/open-feature/flagd-schemas/json" "github.com/open-feature/flagd/core/pkg/logger" "github.com/open-feature/flagd/core/pkg/model" "github.com/open-feature/flagd/core/pkg/store" "github.com/open-feature/flagd/core/pkg/sync" + "github.com/santhosh-tekuri/jsonschema/v6" "github.com/xeipuuv/gojsonschema" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -35,10 +37,49 @@ const ( Disabled = "DISABLED" ) +var compiledSchema *jsonschema.Schema var regBrace *regexp.Regexp func init() { regBrace = regexp.MustCompile("^[^{]*{|}[^}]*$") + + // Create a new JSON Schema compiler + compiler := jsonschema.NewCompiler() + + // Add the Flagd Daemon schema + flagdFile := strings.NewReader(flagd_definitions.FlagdSchema) + schema, err := jsonschema.UnmarshalJSON(flagdFile) + if err != nil { + log.Fatalf("Failed to unmarshal targeting schema: %v", err) + } + if err := compiler.AddResource("https://flagd.dev/schema/v0/flagd.json", schema); err != nil { + log.Fatalf("Failed to add flagd schema: %v", err) + } + + // Add the Flag schema + flagsFile := strings.NewReader(flagd_definitions.FlagSchema) + schema, err = jsonschema.UnmarshalJSON(flagsFile) + if err != nil { + log.Fatalf("Failed to unmarshal targeting schema: %v", err) + } + if err := compiler.AddResource("https://flagd.dev/schema/v0/flags.json", schema); err != nil { + log.Fatalf("Failed to add flags schema: %v", err) + } + + // Add the Targeting schema + targetingFile := strings.NewReader(flagd_definitions.TargetingSchema) + schema, err = jsonschema.UnmarshalJSON(targetingFile) + if err != nil { + log.Fatalf("Failed to unmarshal targeting schema: %v", err) + } + if err := compiler.AddResource("https://flagd.dev/schema/v0/targeting.json", schema); err != nil { + log.Fatalf("Failed to add targeting schema: %v", err) + } + compiledSchema, err = compiler.Compile("https://flagd.dev/schema/v0/flagd.json") + if err != nil { + log.Fatalf("Failed to compile flagd schema: %v", err) + } + } type constraints interface { @@ -429,53 +470,114 @@ func getFlagdProperties(context map[string]any) (flagdProperties, bool) { return p, true } -func loadAndCompileSchema(log *logger.Logger) *gojsonschema.Schema { - schemaLoader := gojsonschema.NewSchemaLoader() +type JsonDef struct { + Flags interface{} `json:"flags"` + Metadata map[string]interface{} `json:"metadata"` +} - // compile dependency schema - targetingSchemaLoader := gojsonschema.NewStringLoader(schema.TargetingSchema) - if err := schemaLoader.AddSchemas(targetingSchemaLoader); err != nil { - log.Warn(fmt.Sprintf("error adding Targeting schema: %s", err)) - } - - // compile root schema - flagdDefinitionsLoader := gojsonschema.NewStringLoader(schema.FlagSchema) - compiledSchema, err := schemaLoader.Compile(flagdDefinitionsLoader) - if err != nil { - log.Warn(fmt.Sprintf("error compiling FlagdDefinitions schema: %s", err)) - } - - return compiledSchema +type Flag struct { + model.Flag + Key string `json:"key,omitempty"` + FlagSetId string `json:"flagSetId,omitempty"` } // configToFlagDefinition convert string configurations to flags and store them to pointer newFlags func configToFlagDefinition(log *logger.Logger, config string, definition *Definition) error { - compiledSchema := loadAndCompileSchema(log) - - flagStringLoader := gojsonschema.NewStringLoader(config) - - result, err := compiledSchema.Validate(flagStringLoader) - if err != nil { - log.Logger.Warn(fmt.Sprintf("failed to execute JSON schema validation: %s", err)) - } else if !result.Valid() { - log.Logger.Warn(fmt.Sprintf( - "flag definition does not conform to the schema; validation errors: %s", buildErrorString(result.Errors()), - )) - } - + // Transpose evaluators and unmarshal directly into JsonDef transposedConfig, err := transposeEvaluators(config) if err != nil { return fmt.Errorf("transposing evaluators: %w", err) } - err = json.Unmarshal([]byte(transposedConfig), &definition) + var intermediateConfig JsonDef + err = json.Unmarshal([]byte(transposedConfig), &intermediateConfig) if err != nil { return fmt.Errorf("unmarshalling provided configurations: %w", err) } + definition.Metadata = intermediateConfig.Metadata + definition.Flags = map[string]model.Flag{} + + // Process flags directly + switch v := intermediateConfig.Flags.(type) { + case map[string]interface{}: // Handle ValidFlags format + for k, e := range v { + flag, err := convertToModelFlag(e) + if err != nil { + return fmt.Errorf("failed to process flag for key %s: %w", k, err) + } + flag.Key = k // Populate the `Key` field explicitly + definition.Flags[k] = flag + } + + case []interface{}: // Handle ValidMapFlags format + for _, value := range v { + flag, err := convertToModelFlag(value) + if err != nil { + return fmt.Errorf("failed to process flag: %w", err) + } + definition.Flags[flag.Key] = flag + } + + default: + return fmt.Errorf("unexpected type for flags property: %T", v) + } + return validateDefaultVariants(definition) } +// Helper function to convert a generic interface{} to model.Flag +func convertToModelFlag(data interface{}) (model.Flag, error) { + flag := model.Flag{} + + // Assert the data is a map[string]interface{} + flagData, ok := data.(map[string]interface{}) + if !ok { + return flag, fmt.Errorf("unexpected type for flag data: %T", data) + } + + // Populate fields of model.Flag + if key, ok := flagData["key"].(string); ok { + flag.Key = key + } + + if state, ok := flagData["state"].(string); ok { + flag.State = state + } + + if defaultVariant, ok := flagData["defaultVariant"].(string); ok { + flag.DefaultVariant = defaultVariant + } + + if variants, ok := flagData["variants"].(map[string]interface{}); ok { + flag.Variants = variants + } + + if targeting, ok := flagData["targeting"].(json.RawMessage); ok { + flag.Targeting = targeting + } else if targetingRaw, ok := flagData["targeting"].(string); ok { + flag.Targeting = json.RawMessage(targetingRaw) + } else if flagData["targeting"] != nil { + marshal, err := json.Marshal(flagData["targeting"]) + if err != nil { + return flag, fmt.Errorf("marshalling targeting: %w", err) + } + + flag.Targeting = json.RawMessage(marshal) + } + + if source, ok := flagData["source"].(string); ok { + flag.Source = source + } + + if metadata, ok := flagData["metadata"].(map[string]interface{}); ok { + // Assuming Metadata is a struct that can be populated directly + flag.Metadata = model.Metadata(metadata) + } + + return flag, nil +} + // validateDefaultVariants returns an error if any of the default variants aren't valid func validateDefaultVariants(flags *Definition) error { for name, flag := range flags.Flags { diff --git c/core/pkg/evaluator/json_test.go i/core/pkg/evaluator/json_test.go index c5c0eaf..76a5619 100644 --- c/core/pkg/evaluator/json_test.go +++ i/core/pkg/evaluator/json_test.go @@ -43,6 +43,23 @@ const ValidFlags = `{ } } }` +const ValidMapFlags = `{ + "flags": [ + { + "key": "validFlag", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "on", + "metadata": { + "flagSetId": "test", + "version": 3 + } + } + ] +}` const NullDefault = `{ "flags": { @@ -400,6 +417,25 @@ func TestGetState_Valid_ContainsFlag(t *testing.T) { t.Fatalf("expected: %s to contain: %s", state, wants) } } +func TestGetState_ValidMap_ContainsFlag(t *testing.T) { + evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags()) + _, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidMapFlags, Source: "testSource"}) + if err != nil { + t.Fatalf("Expected no error") + } + + // get the state + state, err := evaluator.GetState() + if err != nil { + t.Fatalf("expected no error") + } + + // validate it contains the flag + wants := "validFlag" + if !strings.Contains(state, wants) { + t.Fatalf("expected: %s to contain: %s", state, wants) + } +} func TestSetState_Invalid_Error(t *testing.T) { evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags()) diff --git c/schemas i/schemas index 08b4c52..3a4396c 160000 --- c/schemas +++ i/schemas @@ -1 +1 @@ -Subproject commit 08b4c52d3b86d686f11e74322dbfee775db91656 +Subproject commit 3a4396c00de561b012ee0334138de46a48a24c8a
Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
…ich is causing more issues Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
1132bac
to
96c4da3
Compare
Signed-off-by: Simon Schrottner <[email protected]>
05dc7d4
to
6907545
Compare
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
closed in favor of #1797 |
this is my naive attempt of adding flagmap configuration support. i am most likely missing a few crucial parts here, but i think this is overall doing what it should. Before i continue i want to gather some feedback and insights