Skip to content

Commit

Permalink
Implement flag metadata (#3196)
Browse files Browse the repository at this point in the history
  • Loading branch information
kvnhmn authored Jul 25, 2024
1 parent 140b023 commit 489e492
Show file tree
Hide file tree
Showing 27 changed files with 1,445 additions and 1,006 deletions.
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Then you need to enable CGO.

1. Clone this repo: `git clone https://github.com/flipt-io/flipt`.
1. Run `mage bootstrap` to install required development tools. See [#bootstrap](#bootstrap) below.
1. Run `mage go:test` to execute the Go test suite.
1. Run `mage go:test` to execute the Go test suite. For more information on tests, see also [here](build/README.md)
1. Run `mage` to build the binary with embedded assets.
1. Run `mage -l` to see a full list of possible commands.

Expand Down
1 change: 1 addition & 0 deletions config/migrations/cockroachdb/12_flag_metadata.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE flags ADD COLUMN metadata JSON;
1 change: 1 addition & 0 deletions config/migrations/mysql/14_flag_metadata.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE flags ADD COLUMN metadata JSON AFTER enabled;
1 change: 1 addition & 0 deletions config/migrations/postgres/15_flag_metadata.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE flags ADD COLUMN metadata JSON;
1 change: 1 addition & 0 deletions config/migrations/sqlite3/14_flag_metadata.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE flags ADD COLUMN metadata JSON;
3 changes: 3 additions & 0 deletions core/validation/flipt.cue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ close({
type: "BOOLEAN_FLAG_TYPE" | *"VARIANT_FLAG_TYPE"
#FlagBoolean | *{}
}
if version == "1.3" {
metadata: [string]: (string | int | bool | float)
}
}

#FlagBoolean: {
Expand Down
6 changes: 6 additions & 0 deletions core/validation/flipt.json
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,12 @@
"items": {
"$ref": "#/$defs/rule"
}
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": ["string", "number", "boolean"]
}
}
},
"allOf": [
Expand Down
41 changes: 41 additions & 0 deletions core/validation/testdata/valid_metadata_v3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
version: "1.3"
namespace: default
flags:
- key: flipt
name: flipt
description: flipt
enabled: false
variants:
- key: flipt
name: flipt
- key: flipt
name: flipt
default: true
rules:
- segment: internal-users
distributions:
- variant: fromFlipt
rollout: 100
- segment: all-users
distributions:
- variant: fromFlipt2
rollout: 100
metadata:
label: ui
area: 32
hidden: true
radar: 3.2
segments:
- key: all-users
name: All Users
description: All Users
match_type: ALL_MATCH_TYPE
- key: internal-users
name: Internal Users
description: All internal users at flipt.
constraints:
- type: STRING_COMPARISON_TYPE
property: organization
operator: eq
value: flipt
match_type: ALL_MATCH_TYPE
14 changes: 14 additions & 0 deletions core/validation/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ func TestValidate_DefaultVariant_V3(t *testing.T) {
assert.NoError(t, err)
}

func TestValidate_Metadata_V3(t *testing.T) {
const file = "testdata/valid_metadata_v3.yaml"
f, err := os.Open(file)
require.NoError(t, err)

defer f.Close()

v, err := NewFeaturesValidator()
require.NoError(t, err)

err = v.Validate(file, f)
assert.NoError(t, err)
}

func TestValidate_YAML_Stream(t *testing.T) {
const file = "testdata/valid_yaml_stream.yaml"
f, err := os.Open(file)
Expand Down
17 changes: 9 additions & 8 deletions internal/ext/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ type Document struct {
}

type Flag struct {
Key string `yaml:"key,omitempty" json:"key,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Enabled bool `yaml:"enabled" json:"enabled"`
Variants []*Variant `yaml:"variants,omitempty" json:"variants,omitempty"`
Rules []*Rule `yaml:"rules,omitempty" json:"rules,omitempty"`
Rollouts []*Rollout `yaml:"rollouts,omitempty" json:"rollouts,omitempty"`
Key string `yaml:"key,omitempty" json:"key,omitempty"`
Name string `yaml:"name,omitempty" json:"name,omitempty"`
Type string `yaml:"type,omitempty" json:"type,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Enabled bool `yaml:"enabled" json:"enabled"`
Metadata map[string]any `yaml:"metadata,omitempty" json:"metadata,omitempty"`
Variants []*Variant `yaml:"variants,omitempty" json:"variants,omitempty"`
Rules []*Rule `yaml:"rules,omitempty" json:"rules,omitempty"`
Rollouts []*Rollout `yaml:"rollouts,omitempty" json:"rollouts,omitempty"`
}

type Variant struct {
Expand Down
3 changes: 2 additions & 1 deletion internal/ext/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (e *Exporter) Export(ctx context.Context, encoding Encoding, w io.Writer) e

defer enc.Close()

var namespaces = e.namespaces
namespaces := e.namespaces

// If allNamespaces is "true", then retrieve all the namespaces, and store them in a string slice.
if e.allNamespaces {
Expand Down Expand Up @@ -136,6 +136,7 @@ func (e *Exporter) Export(ctx context.Context, encoding Encoding, w io.Writer) e
Type: f.Type.String(),
Description: f.Description,
Enabled: f.Enabled,
Metadata: f.Metadata.AsMap(),
}

// map variant id => variant key
Expand Down
10 changes: 10 additions & 0 deletions internal/ext/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.flipt.io/flipt/rpc/flipt"
"google.golang.org/protobuf/types/known/structpb"
)

type mockLister struct {
Expand Down Expand Up @@ -69,6 +70,13 @@ func (m mockLister) ListRollouts(_ context.Context, listRequest *flipt.ListRollo
return &flipt.RolloutList{}, nil
}

func newStruct(t testing.TB, m map[string]any) *structpb.Struct {
t.Helper()
value, err := structpb.NewStruct(m)
require.NoError(t, err)
return value
}

func TestExport(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -117,13 +125,15 @@ func TestExport(t *testing.T) {
Key: "foo",
},
},
Metadata: newStruct(t, map[string]any{"label": "variant", "area": true}),
},
{
Key: "flag2",
Name: "flag2",
Type: flipt.FlagType_BOOLEAN_FLAG_TYPE,
Description: "a boolean flag",
Enabled: false,
Metadata: newStruct(t, map[string]any{"label": "bool", "area": 12}),
},
},
},
Expand Down
19 changes: 11 additions & 8 deletions internal/ext/importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"go.flipt.io/flipt/rpc/flipt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
)

type Creator interface {
Expand Down Expand Up @@ -56,7 +57,7 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
idx := 0

for {
var doc = new(Document)
doc := new(Document)
if err := dec.Decode(doc); err != nil {
if errors.Is(err, io.EOF) {
break
Expand Down Expand Up @@ -86,13 +87,12 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
}
}

var namespace = doc.Namespace
namespace := doc.Namespace

if namespace != "" && namespace != flipt.DefaultNamespace {
_, err := i.creator.GetNamespace(ctx, &flipt.GetNamespaceRequest{
Key: namespace,
})

if err != nil {
if status.Code(err) != codes.NotFound && !errs.AsMatch[errs.ErrNotFound](err) {
return err
Expand Down Expand Up @@ -149,6 +149,14 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
NamespaceKey: namespace,
}

if f.Metadata != nil {
metadata, err := structpb.NewStruct(f.Metadata)
if err != nil {
return err
}
req.Metadata = metadata
}

// support explicitly setting flag type from 1.1
if f.Type != "" {
if err := ensureFieldSupported("flag.type", v1_1, version); err != nil {
Expand Down Expand Up @@ -197,7 +205,6 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
Attachment: string(out),
NamespaceKey: namespace,
})

if err != nil {
return fmt.Errorf("creating variant: %w", err)
}
Expand Down Expand Up @@ -238,7 +245,6 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
MatchType: flipt.MatchType(flipt.MatchType_value[s.MatchType]),
NamespaceKey: namespace,
})

if err != nil {
return fmt.Errorf("creating segment: %w", err)
}
Expand All @@ -256,7 +262,6 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
Value: c.Value,
NamespaceKey: namespace,
})

if err != nil {
return fmt.Errorf("creating constraint: %w", err)
}
Expand Down Expand Up @@ -300,7 +305,6 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
}

rule, err := i.creator.CreateRule(ctx, fcr)

if err != nil {
return fmt.Errorf("creating rule: %w", err)
}
Expand All @@ -322,7 +326,6 @@ func (i *Importer) Import(ctx context.Context, enc Encoding, r io.Reader, skipEx
Rollout: d.Rollout,
NamespaceKey: namespace,
})

if err != nil {
return fmt.Errorf("creating distribution: %w", err)
}
Expand Down
90 changes: 90 additions & 0 deletions internal/ext/importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ func (m *mockCreator) CreateFlag(ctx context.Context, r *flipt.CreateFlagRequest
Description: r.Description,
Type: r.Type,
Enabled: r.Enabled,
Metadata: r.Metadata,
}, nil
}

Expand Down Expand Up @@ -926,6 +927,95 @@ func TestImport(t *testing.T) {
listSegmentResps: []*flipt.SegmentList{},
},
},
{
name: "import v1.3",
path: "testdata/import_v1_3",
expected: &mockCreator{
createflagReqs: []*flipt.CreateFlagRequest{
{
Key: "flag1",
Name: "flag1",
Description: "description",
Type: flipt.FlagType_VARIANT_FLAG_TYPE,
Enabled: true,
Metadata: newStruct(t, map[string]any{"label": "variant", "area": true}),
},
{
Key: "flag2",
Name: "flag2",
Description: "a boolean flag",
Type: flipt.FlagType_BOOLEAN_FLAG_TYPE,
Enabled: false,
Metadata: newStruct(t, map[string]any{"label": "bool", "area": 12}),
},
},
variantReqs: []*flipt.CreateVariantRequest{
{
FlagKey: "flag1",
Key: "variant1",
Name: "variant1",
Description: "variant description",
Attachment: compact(t, variantAttachment),
},
},
segmentReqs: []*flipt.CreateSegmentRequest{
{
Key: "segment1",
Name: "segment1",
Description: "description",
MatchType: flipt.MatchType_ANY_MATCH_TYPE,
},
},
constraintReqs: []*flipt.CreateConstraintRequest{
{
SegmentKey: "segment1",
Type: flipt.ComparisonType_STRING_COMPARISON_TYPE,
Property: "fizz",
Operator: "neq",
Value: "buzz",
},
},
ruleReqs: []*flipt.CreateRuleRequest{
{
FlagKey: "flag1",
SegmentKey: "segment1",
Rank: 1,
},
},
distributionReqs: []*flipt.CreateDistributionRequest{
{
RuleId: "static_rule_id",
VariantId: "static_variant_id",
FlagKey: "flag1",
Rollout: 100,
},
},
rolloutReqs: []*flipt.CreateRolloutRequest{
{
FlagKey: "flag2",
Description: "enabled for internal users",
Rank: 1,
Rule: &flipt.CreateRolloutRequest_Segment{
Segment: &flipt.RolloutSegment{
SegmentKey: "internal_users",
Value: true,
},
},
},
{
FlagKey: "flag2",
Description: "enabled for 50%",
Rank: 2,
Rule: &flipt.CreateRolloutRequest_Threshold{
Threshold: &flipt.RolloutThreshold{
Percentage: 50.0,
Value: true,
},
},
},
},
},
},
}

for _, tc := range tests {
Expand Down
12 changes: 10 additions & 2 deletions internal/ext/testdata/export.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
"operator": "AND_SEGMENT_OPERATOR"
}
}
]
],
"metadata": {
"label": "variant",
"area": true
}
},
{
"key": "flag2",
Expand All @@ -55,7 +59,11 @@
"description": "enabled for 50%",
"threshold": { "percentage": 50, "value": true }
}
]
],
"metadata": {
"label": "bool",
"area": 12
}
}
],
"segments": [
Expand Down
Loading

0 comments on commit 489e492

Please sign in to comment.