Skip to content

Commit 930c308

Browse files
Merge branch 'main' into dependabot/go_modules/github.com/aws/aws-sdk-go-v2/feature/s3/manager-1.17.65
2 parents b9f9696 + d700ac2 commit 930c308

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1536
-341
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ repos:
55
- id: gofumpt
66

77
- repo: https://github.com/golangci/golangci-lint
8-
rev: v1.59.0
8+
rev: v1.64.6
99
hooks:
1010
- id: golangci-lint
1111
entry: golangci-lint run --enable-only=gci --fix

cmd/cli/evaluate/evaluate_cmd.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ var (
1717
func NewEvaluateCmd() *cobra.Command {
1818
evaluateCmd := &cobra.Command{
1919
Use: "evaluate",
20-
Short: "Evaluate feature flags based on configuration and context",
21-
Long: "Evaluate feature flags based on configuration and context," +
20+
Short: "⚙️ Evaluate feature flags based on configuration and context",
21+
Long: "⚙️ Evaluate feature flags based on configuration and context," +
2222
" if no specific flag requested it will evaluate all flags",
2323
RunE: func(cmd *cobra.Command, args []string) error {
2424
return runEvaluate(cmd, args, evalFlagFormat, evalConfigFile, evalFlag, evalCtx)

cmd/cli/generate/generate_cmd.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package generate
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/thomaspoignant/go-feature-flag/cmd/cli/generate/manifest"
6+
)
7+
8+
func NewGenerateCmd() *cobra.Command {
9+
g := &cobra.Command{
10+
Use: "generate",
11+
Short: "🏗️ Generate GO Feature Flag related files",
12+
Long: `🏗️ Generate GO Feature Flag relates files (examples: flag manifest, ...)`,
13+
}
14+
g.AddCommand(manifest.NewManifestCmd())
15+
return g
16+
}

cmd/cli/generate/manifest/manifest.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package manifest
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/thomaspoignant/go-feature-flag/cmd/cli/generate/manifest/model"
9+
"github.com/thomaspoignant/go-feature-flag/cmd/cli/helper"
10+
"github.com/thomaspoignant/go-feature-flag/model/dto"
11+
)
12+
13+
func NewManifest(configFile string, configFormat string, flagManifestDestination string) (Manifest, error) {
14+
if flagManifestDestination == "" {
15+
return Manifest{}, fmt.Errorf("--flag_manifest_destination is mandatory")
16+
}
17+
flagDTOs, err := helper.LoadConfigFile(configFile, configFormat, helper.ConfigFileDefaultLocations)
18+
if err != nil {
19+
return Manifest{}, err
20+
}
21+
return Manifest{
22+
dtos: flagDTOs,
23+
flagManifestDestination: flagManifestDestination,
24+
}, nil
25+
}
26+
27+
type Manifest struct {
28+
dtos map[string]dto.DTO
29+
flagManifestDestination string
30+
}
31+
32+
// Generate the manifest file
33+
func (m *Manifest) Generate() (helper.Output, error) {
34+
definitions, output, err := m.generateDefinitions(m.dtos)
35+
if err != nil {
36+
return output, err
37+
}
38+
definitionsJSON, err := m.toJSON(definitions)
39+
if err != nil {
40+
return output, err
41+
}
42+
// if we have a destination we write the JSON in the file
43+
err = m.storeManifest(definitionsJSON)
44+
if err != nil {
45+
return output, err
46+
}
47+
return output.Add("🎉 Manifest has been created", helper.InfoLevel), nil
48+
}
49+
50+
// generateDefinitions will generate the definitions from the flagDTOs
51+
func (m *Manifest) generateDefinitions(flagDTOs map[string]dto.DTO) (
52+
model.FlagManifest, helper.Output, error) {
53+
definitions := make(map[string]model.FlagDefinition)
54+
output := helper.Output{}
55+
for flagKey, flagDTO := range flagDTOs {
56+
flag := dto.ConvertDtoToInternalFlag(flagDTO)
57+
flagType, err := helper.GetFlagTypeFromVariations(flag.GetVariations())
58+
if err != nil {
59+
return model.FlagManifest{}, output,
60+
fmt.Errorf("invalid configuration for flag %s: %s", flagKey, err.Error())
61+
}
62+
63+
metadata := flag.GetMetadata()
64+
description, ok := metadata["description"].(string)
65+
if !ok {
66+
description = ""
67+
}
68+
69+
defaultValue, ok := metadata["defaultValue"]
70+
if !ok {
71+
output.Add(fmt.Sprintf("flag %s ignored: no default value provided", flagKey), helper.WarnLevel)
72+
continue
73+
}
74+
definitions[flagKey] = model.FlagDefinition{
75+
FlagType: flagType,
76+
DefaultValue: defaultValue,
77+
Description: description,
78+
}
79+
}
80+
return model.FlagManifest{Flags: definitions}, output, nil
81+
}
82+
83+
// toJSON will convert the definitions to a JSON string
84+
func (m *Manifest) toJSON(manifest model.FlagManifest) (string, error) {
85+
definitionsJSON, err := json.MarshalIndent(manifest, "", " ")
86+
if err != nil {
87+
return "", err
88+
}
89+
return string(definitionsJSON), nil
90+
}
91+
92+
func (m *Manifest) storeManifest(definitionsJSON string) error {
93+
return os.WriteFile(m.flagManifestDestination, []byte(definitionsJSON), 0600)
94+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package manifest
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var (
8+
manifestFlagFormat string
9+
manifestConfigFile string
10+
flagManifestDestination string
11+
)
12+
13+
func NewManifestCmd() *cobra.Command {
14+
manifestCmd := &cobra.Command{
15+
Use: "manifest",
16+
Short: "📄 (experimental) Generate an OpenFeature flag manifest based on your flags configuration.",
17+
Long: "📄 (experimental) Generate an OpenFeature flag manifest based on your flags configuration. " +
18+
"⚠️ note that this is an experimental feature and we may change this command line without warning.",
19+
20+
RunE: func(cmd *cobra.Command, _ []string) error {
21+
m, _ := NewManifest(manifestConfigFile, manifestFlagFormat, flagManifestDestination)
22+
output, err := m.Generate()
23+
if err != nil {
24+
cmd.SilenceUsage = true
25+
return err
26+
}
27+
output.PrintLines(cmd)
28+
return nil
29+
},
30+
}
31+
manifestCmd.Flags().StringVarP(&manifestFlagFormat,
32+
"format", "f", "yaml", "Format of your input file (YAML, JSON or TOML)")
33+
manifestCmd.Flags().StringVarP(&manifestConfigFile,
34+
"config", "c", "", "Location of your GO Feature Flag local configuration file")
35+
manifestCmd.Flags().StringVar(&flagManifestDestination,
36+
"flag_manifest_destination", "", "Destination of your flag manifest file. "+
37+
"If not provided, the manifest will be printed to the console.")
38+
_ = manifestCmd.MarkFlagRequired("flag_manifest_destination")
39+
return manifestCmd
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package manifest_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"github.com/thomaspoignant/go-feature-flag/cmd/cli/generate/manifest"
10+
)
11+
12+
func TestManifestCmd(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
args []string
16+
expectedManifest string
17+
expectedOutput string
18+
assertError assert.ErrorAssertionFunc
19+
}{
20+
{
21+
name: "should return success if everything is ok",
22+
args: []string{"--config=testdata/input/flag.goff.yaml"},
23+
expectedManifest: "testdata/output/flag.goff.json",
24+
expectedOutput: "🎉 Manifest has been created\n",
25+
assertError: assert.NoError,
26+
},
27+
{
28+
name: "should ignore flag without value",
29+
args: []string{"--config=testdata/input/flag-no-default.yaml"},
30+
expectedManifest: "testdata/output/flag-no-default.json",
31+
expectedOutput: "⚠️ flag test-flag ignored: no default value provided\n🎉 Manifest has been created\n",
32+
assertError: assert.NoError,
33+
},
34+
{
35+
name: "should error if flag type is invalid",
36+
args: []string{"--config=testdata/input/flag-invalid-flag-type.yaml"},
37+
assertError: assert.Error,
38+
expectedOutput: "Error: invalid configuration for flag test-flag: impossible to find type\n",
39+
},
40+
{
41+
name: "should have int as type if float with .0 and int are mixed",
42+
args: []string{"--config=testdata/input/flag-int-as-float.yaml"},
43+
assertError: assert.NoError,
44+
expectedManifest: "testdata/output/flag-int-as-float.json",
45+
expectedOutput: "🎉 Manifest has been created\n",
46+
},
47+
{
48+
name: "should have float as type if 1 float and int are mixed",
49+
args: []string{"--config=testdata/input/flag-float-and-int.yaml"},
50+
assertError: assert.NoError,
51+
expectedManifest: "testdata/output/flag-float-and-int.json",
52+
expectedOutput: "🎉 Manifest has been created\n",
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
t.Run(tt.name, func(t *testing.T) {
58+
tmpManifest, err := os.CreateTemp("", "temp")
59+
os.Remove(tmpManifest.Name())
60+
61+
require.NoError(t, err)
62+
63+
redirectionStd, err := os.CreateTemp("", "temp")
64+
require.NoError(t, err)
65+
defer func() {
66+
_ = os.Remove(redirectionStd.Name())
67+
}()
68+
69+
tt.args = append(tt.args, "--flag_manifest_destination", tmpManifest.Name())
70+
71+
cmd := manifest.NewManifestCmd()
72+
cmd.SetErr(redirectionStd)
73+
cmd.SetOut(redirectionStd)
74+
cmd.SetArgs(tt.args)
75+
err = cmd.Execute()
76+
77+
tt.assertError(t, err)
78+
79+
output, err := os.ReadFile(redirectionStd.Name())
80+
assert.NoError(t, err)
81+
assert.Equal(t, tt.expectedOutput, string(output), "output is not expected")
82+
83+
if tt.expectedManifest != "" {
84+
wantManifest, err := os.ReadFile(tt.expectedManifest)
85+
assert.NoError(t, err)
86+
gotManifest, err := os.ReadFile(tmpManifest.Name())
87+
assert.NoError(t, err)
88+
assert.Equal(t, string(wantManifest), string(gotManifest), "manifest is not expected")
89+
}
90+
})
91+
}
92+
}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package manifest_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/thomaspoignant/go-feature-flag/cmd/cli/generate/manifest"
9+
)
10+
11+
func TestNewManifestCmd(t *testing.T) {
12+
type args struct {
13+
config string
14+
format string
15+
setDestination bool
16+
}
17+
tests := []struct {
18+
name string
19+
args args
20+
errorAssert assert.ErrorAssertionFunc
21+
}{
22+
{
23+
name: "should error if config file does not exist",
24+
args: args{
25+
config: "testdata/invalid.yaml",
26+
setDestination: true,
27+
},
28+
errorAssert: assert.Error,
29+
},
30+
{
31+
name: "should not error if config file exists",
32+
args: args{
33+
config: "testdata/input/flag.goff.yaml",
34+
setDestination: true,
35+
},
36+
errorAssert: assert.NoError,
37+
},
38+
{
39+
name: "should not error no destination provided",
40+
args: args{
41+
config: "testdata/input/flag.goff.yaml",
42+
setDestination: false,
43+
},
44+
errorAssert: assert.Error,
45+
},
46+
}
47+
48+
for _, tt := range tests {
49+
t.Run(tt.name, func(t *testing.T) {
50+
destination := ""
51+
if tt.args.setDestination {
52+
f, err := os.CreateTemp("", "temp")
53+
assert.NoError(t, err)
54+
destination = f.Name()
55+
defer func() { _ = os.Remove(destination) }()
56+
}
57+
_, err := manifest.NewManifest(tt.args.config, tt.args.format, destination)
58+
tt.errorAssert(t, err)
59+
})
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package model
2+
3+
type FlagDefinition struct {
4+
FlagType FlagType `json:"flagType"`
5+
DefaultValue interface{} `json:"defaultValue"`
6+
Description string `json:"description"`
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package model
2+
3+
type FlagManifest struct {
4+
Flags map[string]FlagDefinition `json:"flags"`
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package model
2+
3+
type FlagType string
4+
5+
const (
6+
FlagTypeBoolean FlagType = "boolean"
7+
FlagTypeString FlagType = "string"
8+
FlagTypeInteger FlagType = "integer"
9+
FlagTypeFloat FlagType = "float"
10+
FlagTypeObject FlagType = "object"
11+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
test-flag:
2+
variations:
3+
Default: 1.4
4+
False: 2
5+
True: 3
6+
targeting:
7+
- name: rule1
8+
query: key eq "random-key"
9+
percentage:
10+
False: 0
11+
True: 100
12+
defaultRule:
13+
name: defaultRule
14+
variation: Default
15+
metadata:
16+
description: this is a simple feature flag
17+
issue-link: https://jira.xxx/GOFF-01
18+
defaultValue: 25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
test-flag:
2+
variations:
3+
Default: 1.0
4+
False: 2
5+
True: 3
6+
targeting:
7+
- name: rule1
8+
query: key eq "random-key"
9+
percentage:
10+
False: 0
11+
True: 100
12+
defaultRule:
13+
name: defaultRule
14+
variation: Default
15+
metadata:
16+
description: this is a simple feature flag
17+
issue-link: https://jira.xxx/GOFF-01
18+
defaultValue: 25

0 commit comments

Comments
 (0)