From 2f2665dc3a3ae9a97a7f10c4569c5a7cc9f1fd6a Mon Sep 17 00:00:00 2001 From: deamondev Date: Tue, 23 Sep 2025 19:29:21 +0200 Subject: [PATCH 01/29] add support for github retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate.go | 11 ++++++-- cmd/cli/evaluate/evaluate_cmd.go | 47 ++++++++++++++++++++++++++++--- cmd/cli/evaluate/evaluate_test.go | 15 +++++----- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/cmd/cli/evaluate/evaluate.go b/cmd/cli/evaluate/evaluate.go index 30bb45e0f17..fe13e15f3b4 100644 --- a/cmd/cli/evaluate/evaluate.go +++ b/cmd/cli/evaluate/evaluate.go @@ -7,24 +7,29 @@ import ( "time" ffclient "github.com/thomaspoignant/go-feature-flag" + "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" + retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/internal/utils" "github.com/thomaspoignant/go-feature-flag/model" - "github.com/thomaspoignant/go-feature-flag/retriever/fileretriever" ) type evaluate struct { - config string + retrieverConf retrieverconf.RetrieverConf fileFormat string flag string evaluationCtx string } func (e evaluate) Evaluate() (map[string]model.RawVarResult, error) { + r, err := retrieverInit.InitRetriever(&e.retrieverConf) + if err != nil { + return nil, err + } c := ffclient.Config{ PollingInterval: 10 * time.Minute, DisableNotifierOnInit: true, Context: context.Background(), - Retriever: &fileretriever.Retriever{Path: e.config}, + Retriever: r, FileFormat: e.fileFormat, } diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 151d3c4012b..b19f29333fa 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -5,11 +5,18 @@ import ( "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" + "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" ) var ( evalFlagFormat string + kind string evalConfigFile string + path string + authToken string + githubToken string + repositorySlug string + branch string evalFlag string evalCtx string ) @@ -21,20 +28,52 @@ func NewEvaluateCmd() *cobra.Command { Long: "⚙️ Evaluate feature flags based on configuration and context," + " if no specific flag requested it will evaluate all flags", RunE: func(cmd *cobra.Command, args []string) error { - return runEvaluate(cmd, args, evalFlagFormat, evalConfigFile, evalFlag, evalCtx) + retrieverConf := retrieverconf.RetrieverConf{ + Kind: retrieverconf.RetrieverKind(kind), + RepositorySlug: repositorySlug, + Branch: branch, + Path: func() string { + if path != "" { + return path + } + return evalConfigFile + }(), + GithubToken: githubToken, + AuthToken: authToken, + } + + err := retrieverConf.IsValid() + if err != nil { + return err + } + + return runEvaluate(cmd, args, evalFlagFormat, retrieverConf, evalFlag, evalCtx) }, SilenceUsage: true, SilenceErrors: true, } evaluateCmd.Flags().StringVarP(&evalFlagFormat, "format", "f", "yaml", "Format of your input file (YAML, JSON or TOML)") + evaluateCmd.Flags().StringVarP(&kind, + "kind", "k", "file", "Kind of the configuration file (file, http, redis, gitlab, k8s, ...)") evaluateCmd.Flags().StringVarP(&evalConfigFile, "config", "c", "", "Location of your GO Feature Flag local configuration file") + evaluateCmd.Flags().StringVarP(&path, "path", "p", "", + "Path to your GO Feature Flag configuration file (local or remote)") + evaluateCmd.Flags().StringVar(&authToken, + "auth-token", "", "Authentication token to access your private configuration file") + evaluateCmd.Flags().StringVar(&githubToken, + "github-token", "", "Authentication token to access your private configuration file on GitHub") + evaluateCmd.Flags().StringVar(&repositorySlug, + "repository-slug", "", "Repository slug to access your private configuration file on GitHub") + evaluateCmd.Flags().StringVar(&branch, + "branch", "", "Branch to access your private configuration file on GitHub") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") - _ = evaluateCmd.MarkFlagRequired("config") + _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") + _ = evaluateCmd.Flags() return evaluateCmd } @@ -42,12 +81,12 @@ func runEvaluate( cmd *cobra.Command, _ []string, flagFormat string, - configFile string, + retrieverConf retrieverconf.RetrieverConf, flag string, ctx string) error { output := helper.Output{} e := evaluate{ - config: configFile, + retrieverConf: retrieverConf, fileFormat: flagFormat, flag: flag, evaluationCtx: ctx, diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 2657c5976be..f4e09cced61 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" "github.com/thomaspoignant/go-feature-flag/model" ) @@ -18,7 +19,7 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should error is config file does not exist", evaluate: evaluate{ - config: "testdata/invalid.yaml", + retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/invalid.yaml"}, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -29,9 +30,9 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should error if no evaluation context provided", evaluate: evaluate{ - config: "testdata/flag.goff.yaml", - fileFormat: "yaml", - flag: "test-flag", + retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, + fileFormat: "yaml", + flag: "test-flag", }, wantErr: assert.Error, expectedErr: "invalid evaluation context (missing targeting key)", @@ -39,7 +40,7 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should error if evaluation context provided has no targeting key", evaluate: evaluate{ - config: "testdata/flag.goff.yaml", + retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"id": "user-123"}`, @@ -50,7 +51,7 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should evaluate a single flag if flag name is provided", evaluate: evaluate{ - config: "testdata/flag.goff.yaml", + retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -77,7 +78,7 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should evaluate all flags if flag name is not provided", evaluate: evaluate{ - config: "testdata/flag.goff.yaml", + retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, fileFormat: "yaml", evaluationCtx: `{"targetingKey": "user-123"}`, }, From 812c65d9cf319447a4261f2cb424a0c8c290d04d Mon Sep 17 00:00:00 2001 From: deamondev Date: Thu, 25 Sep 2025 13:57:27 +0200 Subject: [PATCH 02/29] refactor evaluate struct Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate.go | 11 +-- cmd/cli/evaluate/evaluate_cmd.go | 11 ++- cmd/cli/evaluate/evaluate_cmd_test.go | 29 +++++- cmd/cli/evaluate/evaluate_test.go | 127 +++++++++++++++++++++----- 4 files changed, 142 insertions(+), 36 deletions(-) diff --git a/cmd/cli/evaluate/evaluate.go b/cmd/cli/evaluate/evaluate.go index fe13e15f3b4..8b0b942a711 100644 --- a/cmd/cli/evaluate/evaluate.go +++ b/cmd/cli/evaluate/evaluate.go @@ -7,29 +7,24 @@ import ( "time" ffclient "github.com/thomaspoignant/go-feature-flag" - "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" - retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/internal/utils" "github.com/thomaspoignant/go-feature-flag/model" + "github.com/thomaspoignant/go-feature-flag/retriever" ) type evaluate struct { - retrieverConf retrieverconf.RetrieverConf + retriever retriever.Retriever fileFormat string flag string evaluationCtx string } func (e evaluate) Evaluate() (map[string]model.RawVarResult, error) { - r, err := retrieverInit.InitRetriever(&e.retrieverConf) - if err != nil { - return nil, err - } c := ffclient.Config{ PollingInterval: 10 * time.Minute, DisableNotifierOnInit: true, Context: context.Background(), - Retriever: r, + Retriever: e.retriever, FileFormat: e.fileFormat, } diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index b19f29333fa..a60db2c2e8a 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" + retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" ) var ( @@ -73,6 +74,7 @@ func NewEvaluateCmd() *cobra.Command { evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") + _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() return evaluateCmd } @@ -85,12 +87,19 @@ func runEvaluate( flag string, ctx string) error { output := helper.Output{} + + r, err := retrieverInit.InitRetriever(&retrieverConf) + if err != nil { + return err + } + e := evaluate{ - retrieverConf: retrieverConf, + retriever: r, fileFormat: flagFormat, flag: flag, evaluationCtx: ctx, } + result, err := e.Evaluate() if err != nil { return err diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 74c5c761add..61affc305cf 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -19,7 +19,9 @@ func TestCmdEvaluate(t *testing.T) { { name: "should return an error if flag does not exists", args: []string{ - "--config", + "--kind", + "file", + "--path", "testdata/flag.goff.yaml", "--flag", "non-existent-flag", @@ -34,7 +36,9 @@ func TestCmdEvaluate(t *testing.T) { { name: "should return all flags if no flag is provided", args: []string{ - "--config", + "--kind", + "file", + "--path", "testdata/flag.goff.yaml", "--ctx", `{"targetingKey": "user-123"}`, @@ -47,7 +51,26 @@ func TestCmdEvaluate(t *testing.T) { { name: "should return a single flag if flag name is provided", args: []string{ - "--config", + "--kind", + "file", + "--path", + "testdata/flag.goff.yaml", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/single-flag.json", + }, + { + name: "should return a single flag if flag name is provided using path flag", + args: []string{ + "--kind", + "file", + "--path", "testdata/flag.goff.yaml", "--flag", "test-flag", diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index f4e09cced61..35deb354fc2 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -5,56 +5,87 @@ import ( "github.com/stretchr/testify/assert" "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" + retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/model" + "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" + "github.com/thomaspoignant/go-feature-flag/testutils/mock" ) func Test_evaluate_Evaluate(t *testing.T) { tests := []struct { name string evaluate evaluate + initEvaluate func() (evaluate, error) wantErr assert.ErrorAssertionFunc expectedErr string expectedResult map[string]model.RawVarResult }{ { name: "Should error is config file does not exist", - evaluate: evaluate{ - retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/invalid.yaml"}, - fileFormat: "yaml", - flag: "test-flag", - evaluationCtx: `{"targetingKey": "user-123"}`, + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever(&retrieverconf.RetrieverConf{ + Kind: "file", + Path: "testdata/invalid.yaml", + }) + if err != nil { + return evaluate{}, err + } + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil }, wantErr: assert.Error, expectedErr: "impossible to initialize the retrievers, please check your configuration: impossible to retrieve the flags, please check your configuration: open testdata/invalid.yaml: no such file or directory", }, { name: "Should error if no evaluation context provided", - evaluate: evaluate{ - retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, - fileFormat: "yaml", - flag: "test-flag", + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever(&retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}) + if err != nil { + return evaluate{}, err + } + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + }, nil }, wantErr: assert.Error, expectedErr: "invalid evaluation context (missing targeting key)", }, { name: "Should error if evaluation context provided has no targeting key", - evaluate: evaluate{ - retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, - fileFormat: "yaml", - flag: "test-flag", - evaluationCtx: `{"id": "user-123"}`, + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever(&retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}) + if err != nil { + return evaluate{}, err + } + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"id": "user-123"}`, + }, nil }, wantErr: assert.Error, expectedErr: "invalid evaluation context (missing targeting key)", }, { name: "Should evaluate a single flag if flag name is provided", - evaluate: evaluate{ - retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, - fileFormat: "yaml", - flag: "test-flag", - evaluationCtx: `{"targetingKey": "user-123"}`, + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever(&retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}) + if err != nil { + return evaluate{}, err + } + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil }, wantErr: assert.NoError, expectedResult: map[string]model.RawVarResult{ @@ -77,10 +108,16 @@ func Test_evaluate_Evaluate(t *testing.T) { }, { name: "Should evaluate all flags if flag name is not provided", - evaluate: evaluate{ - retrieverConf: retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}, - fileFormat: "yaml", - evaluationCtx: `{"targetingKey": "user-123"}`, + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever(&retrieverconf.RetrieverConf{Kind: "file", Path: "testdata/flag.goff.yaml"}) + if err != nil { + return evaluate{}, err + } + return evaluate{ + retriever: r, + fileFormat: "yaml", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil }, wantErr: assert.NoError, expectedResult: map[string]model.RawVarResult{ @@ -112,10 +149,52 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, }, + { + name: "new test", + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{Kind: "github", RepositorySlug: "thomaspoignant/go-feature-flag", + GithubToken: "XXX_GH_TOKEN", + Path: "testdata/flag-config.yaml"}) + if err != nil { + return evaluate{}, err + } + + githubRetriever, _ := r.(*githubretriever.Retriever) + githubRetriever.SetHTTPClient(&mock.HTTP{}) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "false_var", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: nil, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - m, err := tt.evaluate.Evaluate() + e, err := tt.initEvaluate() + if err != nil { + tt.wantErr(t, err) + return + } + m, err := e.Evaluate() tt.wantErr(t, err) if tt.expectedErr != "" { From 9bb98da104e5240dec53433ff670b00b667b2c17 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 28 Sep 2025 21:16:22 +0200 Subject: [PATCH 03/29] add gitlab retriever support Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 4 +++ cmd/cli/evaluate/evaluate_test.go | 55 +++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index a60db2c2e8a..d3ec82c27c6 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -18,6 +18,7 @@ var ( githubToken string repositorySlug string branch string + baseURL string evalFlag string evalCtx string ) @@ -41,6 +42,7 @@ func NewEvaluateCmd() *cobra.Command { }(), GithubToken: githubToken, AuthToken: authToken, + BaseURL: baseURL, } err := retrieverConf.IsValid() @@ -69,6 +71,8 @@ func NewEvaluateCmd() *cobra.Command { "repository-slug", "", "Repository slug to access your private configuration file on GitHub") evaluateCmd.Flags().StringVar(&branch, "branch", "", "Branch to access your private configuration file on GitHub") + evaluateCmd.Flags().StringVar(&baseURL, "base-url", "", + "Base URL of your private configuration file on Gitlab") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 35deb354fc2..eabf5ff9688 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -8,6 +8,7 @@ import ( retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/model" "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" + "github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever" "github.com/thomaspoignant/go-feature-flag/testutils/mock" ) @@ -150,18 +151,60 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, { - name: "new test", + name: "Should evaluate a flag from a github repository", initEvaluate: func() (evaluate, error) { r, err := retrieverInit.InitRetriever( - &retrieverconf.RetrieverConf{Kind: "github", RepositorySlug: "thomaspoignant/go-feature-flag", - GithubToken: "XXX_GH_TOKEN", - Path: "testdata/flag-config.yaml"}) + &retrieverconf.RetrieverConf{ + Kind: "github", + RepositorySlug: "thomaspoignant/go-feature-flag", + GithubToken: "XXX_GH_TOKEN", + Path: "testdata/flag-config.yaml"}) if err != nil { return evaluate{}, err } - githubRetriever, _ := r.(*githubretriever.Retriever) - githubRetriever.SetHTTPClient(&mock.HTTP{}) + gitHubRetriever, _ := r.(*githubretriever.Retriever) + gitHubRetriever.SetHTTPClient(&mock.HTTP{}) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "false_var", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: nil, + }, + }, + }, + { + name: "Should evaluate a flag from a gitlab repository", + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{ + Kind: "gitlab", + BaseURL: baseURL, + RepositorySlug: "thomaspoignant/go-feature-flag", + AuthToken: "XXX", + Path: "testdata/flag-config.yaml"}) + if err != nil { + return evaluate{}, err + } + + gitLabRetriever, _ := r.(*gitlabretriever.Retriever) + gitLabRetriever.SetHTTPClient(&mock.HTTP{}) return evaluate{ retriever: r, From a89ab250d16c376c1f7bd41e80078ff4539042c8 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 28 Sep 2025 21:19:07 +0200 Subject: [PATCH 04/29] fix formatting Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index d3ec82c27c6..3b0bdc36d79 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -71,8 +71,8 @@ func NewEvaluateCmd() *cobra.Command { "repository-slug", "", "Repository slug to access your private configuration file on GitHub") evaluateCmd.Flags().StringVar(&branch, "branch", "", "Branch to access your private configuration file on GitHub") - evaluateCmd.Flags().StringVar(&baseURL, "base-url", "", - "Base URL of your private configuration file on Gitlab") + evaluateCmd.Flags().StringVar(&baseURL, + "base-url", "", "Base URL of your private configuration file on Gitlab") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, From 09d5f1edd7c31faf82025125c8813e83a204f2f4 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 28 Sep 2025 21:24:25 +0200 Subject: [PATCH 05/29] add support for bitbucket retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_test.go | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index eabf5ff9688..3394a3d0404 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -7,6 +7,7 @@ import ( "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/model" + "github.com/thomaspoignant/go-feature-flag/retriever/bitbucketretriever" "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" "github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever" "github.com/thomaspoignant/go-feature-flag/testutils/mock" @@ -229,6 +230,46 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, }, + { + name: "Should evaluate a flag from a bitbucket repository", + initEvaluate: func() (evaluate, error) { + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{ + Kind: "bitbucket", + BaseURL: baseURL, + RepositorySlug: "thomaspoignant/go-feature-flag", + AuthToken: "XXX", + Path: "testdata/flag-config.yaml"}) + if err != nil { + return evaluate{}, err + } + + bitBucketRetriever, _ := r.(*bitbucketretriever.Retriever) + bitBucketRetriever.SetHTTPClient(&mock.HTTP{}) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "false_var", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: nil, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 448aa69448fda04ebd72456782a17b046c194703 Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 29 Sep 2025 09:22:18 +0200 Subject: [PATCH 06/29] add support for s3 retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 18 +++++--- cmd/cli/evaluate/evaluate_test.go | 50 ++++++++++++++++++++++ cmd/cli/evaluate/testdata/flag-config.yaml | 32 ++++++++++++++ retriever/s3retrieverv2/retriever.go | 4 ++ 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/flag-config.yaml diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 3b0bdc36d79..60b651acacc 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -19,6 +19,8 @@ var ( repositorySlug string branch string baseURL string + bucket string + item string evalFlag string evalCtx string ) @@ -43,6 +45,8 @@ func NewEvaluateCmd() *cobra.Command { GithubToken: githubToken, AuthToken: authToken, BaseURL: baseURL, + Bucket: bucket, + Item: item, } err := retrieverConf.IsValid() @@ -64,15 +68,19 @@ func NewEvaluateCmd() *cobra.Command { evaluateCmd.Flags().StringVarP(&path, "path", "p", "", "Path to your GO Feature Flag configuration file (local or remote)") evaluateCmd.Flags().StringVar(&authToken, - "auth-token", "", "Authentication token to access your private configuration file") + "auth-token", "", "Authentication token to access your configuration file") evaluateCmd.Flags().StringVar(&githubToken, - "github-token", "", "Authentication token to access your private configuration file on GitHub") + "github-token", "", "Authentication token to access your configuration file on GitHub") evaluateCmd.Flags().StringVar(&repositorySlug, - "repository-slug", "", "Repository slug to access your private configuration file on GitHub") + "repository-slug", "", "Repository slug to access your configuration file on GitHub") evaluateCmd.Flags().StringVar(&branch, - "branch", "", "Branch to access your private configuration file on GitHub") + "branch", "", "Branch to access your configuration file on GitHub") evaluateCmd.Flags().StringVar(&baseURL, - "base-url", "", "Base URL of your private configuration file on Gitlab") + "base-url", "", "Base URL of your configuration file on Gitlab") + evaluateCmd.Flags().StringVar(&bucket, + "bucket", "", "Bucket of your configuration file on S3") + evaluateCmd.Flags().StringVar(&item, + "item", "", "Item of your configuration file on S3") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 3394a3d0404..5c7de08b7bd 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -1,6 +1,7 @@ package evaluate import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -10,6 +11,8 @@ import ( "github.com/thomaspoignant/go-feature-flag/retriever/bitbucketretriever" "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" "github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever" + "github.com/thomaspoignant/go-feature-flag/retriever/s3retrieverv2" + "github.com/thomaspoignant/go-feature-flag/testutils" "github.com/thomaspoignant/go-feature-flag/testutils/mock" ) @@ -270,6 +273,53 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, }, + { + name: "Should evaluate a flag from a S3 repository", + initEvaluate: func() (evaluate, error) { + downloader := &testutils.S3ManagerV2Mock{ + TestDataLocation: "./testdata", + } + + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{ + Kind: "s3", + Bucket: "Bucket", + Item: "valid", + }) + + if err != nil { + return evaluate{}, err + } + + s3Retriever, _ := r.(*s3retrieverv2.Retriever) + s3Retriever.SetDownloader(downloader) + + _ = s3Retriever.Init(context.Background(), nil) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "Default", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: map[string]interface{}{"description": "this is a simple feature flag", + "issue-link": "https://jira.xxx/GOFF-01"}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/testdata/flag-config.yaml b/cmd/cli/evaluate/testdata/flag-config.yaml new file mode 100644 index 00000000000..c7772da0977 --- /dev/null +++ b/cmd/cli/evaluate/testdata/flag-config.yaml @@ -0,0 +1,32 @@ +test-flag: + variations: + Default: false + False: false + True: true + targeting: + - name: rule1 + query: key eq "random-key" + percentage: + False: 0 + True: 100 + defaultRule: + name: defaultRule + variation: Default + metadata: + description: this is a simple feature flag + issue-link: https://jira.xxx/GOFF-01 + +test-flag2: + variations: + Default: false + False: false + True: true + targeting: + - name: rule1 + query: key eq "not-a-key" + percentage: + False: 0 + True: 100 + defaultRule: + name: defaultRule + variation: Default diff --git a/retriever/s3retrieverv2/retriever.go b/retriever/s3retrieverv2/retriever.go index 2a8c31341e8..8accbc6df78 100644 --- a/retriever/s3retrieverv2/retriever.go +++ b/retriever/s3retrieverv2/retriever.go @@ -97,3 +97,7 @@ func (s *Retriever) Retrieve(ctx context.Context) ([]byte, error) { return writerAt.Bytes(), nil } + +func (s *Retriever) SetDownloader(downloader DownloaderAPI) { + s.downloader = downloader +} From 1c05e8728b4d45a74ab9dacbee336b055e5fe720 Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 29 Sep 2025 21:47:10 +0200 Subject: [PATCH 07/29] start adding support for http retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 60b651acacc..a138b412dde 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -2,6 +2,7 @@ package evaluate import ( "encoding/json" + "strings" "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" @@ -21,11 +22,18 @@ var ( baseURL string bucket string item string + url string + method string + body string + headers []string + timeout int64 evalFlag string evalCtx string ) func NewEvaluateCmd() *cobra.Command { + parsedHeaders := parseHeaders() + evaluateCmd := &cobra.Command{ Use: "evaluate", Short: "⚙️ Evaluate feature flags based on configuration and context", @@ -47,6 +55,11 @@ func NewEvaluateCmd() *cobra.Command { BaseURL: baseURL, Bucket: bucket, Item: item, + URL: url, + Timeout: timeout, + HTTPMethod: method, + HTTPBody: body, + HTTPHeaders: parsedHeaders, } err := retrieverConf.IsValid() @@ -81,6 +94,16 @@ func NewEvaluateCmd() *cobra.Command { "bucket", "", "Bucket of your configuration file on S3") evaluateCmd.Flags().StringVar(&item, "item", "", "Item of your configuration file on S3") + evaluateCmd.Flags().StringVar(&url, + "url", "", "URL of your configuration file on HTTP") + evaluateCmd.Flags().StringVar(&method, + "method", "GET", "Method to access your configuration file on HTTP") + evaluateCmd.Flags().StringVar(&body, + "body", "", "Body to access your configuration file on HTTP") + evaluateCmd.Flags().StringArrayVar(&headers, + "header", nil, "Header to access your configuration file on HTTP (may be repeated)") + evaluateCmd.Flags().Int64Var(&timeout, + "timeout", 0, "Timeout in seconds to access your configuration file on HTTP") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, @@ -126,3 +149,20 @@ func runEvaluate( output.PrintLines(cmd) return nil } + +func parseHeaders() map[string][]string { + result := make(map[string][]string) + for _, h := range headers { + parts := strings.SplitN(h, "=", 2) + if len(parts) != 2 { + parts = strings.SplitN(h, ":", 2) + } + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + result[key] = append(result[key], val) + } + return result +} From 8d62a36d56afa7c50e25b80ceffa00baccf093f9 Mon Sep 17 00:00:00 2001 From: deamondev Date: Tue, 30 Sep 2025 19:00:18 +0200 Subject: [PATCH 08/29] add test for http retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_test.go | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 5c7de08b7bd..7cf94aae2da 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -2,6 +2,7 @@ package evaluate import ( "context" + "net/http" "testing" "github.com/stretchr/testify/assert" @@ -11,6 +12,7 @@ import ( "github.com/thomaspoignant/go-feature-flag/retriever/bitbucketretriever" "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" "github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever" + "github.com/thomaspoignant/go-feature-flag/retriever/httpretriever" "github.com/thomaspoignant/go-feature-flag/retriever/s3retrieverv2" "github.com/thomaspoignant/go-feature-flag/testutils" "github.com/thomaspoignant/go-feature-flag/testutils/mock" @@ -320,6 +322,49 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, }, + { + name: "Should evaluate a flag from a HTTP endpoint", + initEvaluate: func() (evaluate, error) { + + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{ + Kind: "http", + URL: "http://localhost.example/file", + HTTPMethod: http.MethodGet, + HTTPBody: "", + HTTPHeaders: nil, + }) + + if err != nil { + return evaluate{}, err + } + + httpRetriever, _ := r.(*httpretriever.Retriever) + httpRetriever.SetHTTPClient(&mock.HTTP{}) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "false_var", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: nil, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 1163fa12ff5795023add4c56d86005691767d994 Mon Sep 17 00:00:00 2001 From: deamondev Date: Wed, 1 Oct 2025 08:48:38 +0200 Subject: [PATCH 09/29] add check flag Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index a138b412dde..b227393c2ff 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -29,6 +29,7 @@ var ( timeout int64 evalFlag string evalCtx string + check bool ) func NewEvaluateCmd() *cobra.Command { @@ -67,7 +68,11 @@ func NewEvaluateCmd() *cobra.Command { return err } - return runEvaluate(cmd, args, evalFlagFormat, retrieverConf, evalFlag, evalCtx) + if check { + return runCheck(cmd, retrieverConf) + } else { + return runEvaluate(cmd, args, evalFlagFormat, retrieverConf, evalFlag, evalCtx) + } }, SilenceUsage: true, SilenceErrors: true, @@ -108,6 +113,8 @@ func NewEvaluateCmd() *cobra.Command { "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") + evaluateCmd.Flags().BoolVar(&check, + "check", false, "Check only mode - it does not perform any evaluation and returns the configuration of retriever") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() @@ -150,6 +157,21 @@ func runEvaluate( return nil } +func runCheck( + cmd *cobra.Command, + retrieverConf retrieverconf.RetrieverConf) error { + output := helper.Output{} + + detailed, err := json.MarshalIndent(retrieverConf, "", "") + if err != nil { + return err + } + + output.Add(string(detailed), helper.DefaultLevel) + output.PrintLines(cmd) + return nil +} + func parseHeaders() map[string][]string { result := make(map[string][]string) for _, h := range headers { From 15052c2f58a4a1ba449026d7a9e510bde410f5f0 Mon Sep 17 00:00:00 2001 From: deamondev Date: Wed, 1 Oct 2025 09:24:13 +0200 Subject: [PATCH 10/29] add test for evaluate cmd http retriever Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 26 +++++++++++----- cmd/cli/evaluate/evaluate_cmd_test.go | 24 +++++++++++++++ cmd/cli/evaluate/evaluate_test.go | 2 +- cmd/cli/evaluate/testdata/res/check.json | 38 ++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/res/check.json diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index b227393c2ff..dcc9dd98821 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -29,18 +29,28 @@ var ( timeout int64 evalFlag string evalCtx string - check bool + checkMode bool ) func NewEvaluateCmd() *cobra.Command { - parsedHeaders := parseHeaders() - evaluateCmd := &cobra.Command{ Use: "evaluate", Short: "⚙️ Evaluate feature flags based on configuration and context", + Example: ` +# Evaluate a specific flag using deprecated flag --config +evaluate --config ./config.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using new flag --path +evaluate --kind file --path ./config.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using http retriever +evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentType: application/json' --header 'X-Auth=Token' --flag flag1 --ctx '{"targetingKey": "user-123"}' +`, Long: "⚙️ Evaluate feature flags based on configuration and context," + " if no specific flag requested it will evaluate all flags", RunE: func(cmd *cobra.Command, args []string) error { + parsedHeaders := parseHeaders() + retrieverConf := retrieverconf.RetrieverConf{ Kind: retrieverconf.RetrieverKind(kind), RepositorySlug: repositorySlug, @@ -68,7 +78,7 @@ func NewEvaluateCmd() *cobra.Command { return err } - if check { + if checkMode { return runCheck(cmd, retrieverConf) } else { return runEvaluate(cmd, args, evalFlagFormat, retrieverConf, evalFlag, evalCtx) @@ -106,15 +116,15 @@ func NewEvaluateCmd() *cobra.Command { evaluateCmd.Flags().StringVar(&body, "body", "", "Body to access your configuration file on HTTP") evaluateCmd.Flags().StringArrayVar(&headers, - "header", nil, "Header to access your configuration file on HTTP (may be repeated)") + "header", nil, "Header to access your configuration file on HTTP (may be repeated). See example of `evaluate` command for usages") evaluateCmd.Flags().Int64Var(&timeout, "timeout", 0, "Timeout in seconds to access your configuration file on HTTP") evaluateCmd.Flags().StringVar(&evalFlag, "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") - evaluateCmd.Flags().BoolVar(&check, - "check", false, "Check only mode - it does not perform any evaluation and returns the configuration of retriever") + evaluateCmd.Flags().BoolVar(&checkMode, + "check-mode", false, "Check only mode - it does not perform any evaluation and returns the configuration of spanned retriever") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() @@ -162,7 +172,7 @@ func runCheck( retrieverConf retrieverconf.RetrieverConf) error { output := helper.Output{} - detailed, err := json.MarshalIndent(retrieverConf, "", "") + detailed, err := json.MarshalIndent(retrieverConf, "", " ") if err != nil { return err } diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 61affc305cf..ef7b0d6d135 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -108,6 +108,30 @@ func TestCmdEvaluate(t *testing.T) { }, wantErr: assert.Error, }, + { + name: "should return configuration of http retriever with headers set properly", + args: []string{ + "--kind", + "http", + "--url", + "http://localhost:8080/config.yaml", + "--header", + "Content-Type: application/json", + "--header", + "X-API-Key: 123456", + "--header", + "X-API-Key: 654321", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check.json", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 7cf94aae2da..49b798c7351 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -45,7 +45,7 @@ func Test_evaluate_Evaluate(t *testing.T) { }, nil }, wantErr: assert.Error, - expectedErr: "impossible to initialize the retrievers, please check your configuration: impossible to retrieve the flags, please check your configuration: open testdata/invalid.yaml: no such file or directory", + expectedErr: "impossible to initialize the retrievers, please checkMode your configuration: impossible to retrieve the flags, please checkMode your configuration: open testdata/invalid.yaml: no such file or directory", }, { name: "Should error if no evaluation context provided", diff --git a/cmd/cli/evaluate/testdata/res/check.json b/cmd/cli/evaluate/testdata/res/check.json new file mode 100644 index 00000000000..fb9ee761436 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check.json @@ -0,0 +1,38 @@ +{ + "Kind": "http", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "http://localhost:8080/config.yaml", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": { + "Content-Type": [ + "application/json" + ], + "X-API-Key": [ + "123456", + "654321" + ] + }, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file From 86ffc801d023a02c72edf9c458939ac3b791f93a Mon Sep 17 00:00:00 2001 From: deamondev Date: Thu, 2 Oct 2025 09:48:51 +0200 Subject: [PATCH 11/29] add more tests Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd_test.go | 52 ++++++++++++++++++- cmd/cli/evaluate/evaluate_test.go | 6 +-- .../evaluate/testdata/res/check-github.json | 30 +++++++++++ .../evaluate/testdata/res/check-gitlab.json | 30 +++++++++++ .../res/{check.json => check-http.json} | 0 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/res/check-github.json create mode 100644 cmd/cli/evaluate/testdata/res/check-gitlab.json rename cmd/cli/evaluate/testdata/res/{check.json => check-http.json} (100%) diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index ef7b0d6d135..64cdd047b2c 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -130,7 +130,57 @@ func TestCmdEvaluate(t *testing.T) { "--check-mode", }, wantErr: assert.NoError, - expectedResult: "testdata/res/check.json", + expectedResult: "testdata/res/check-http.json", + }, + { + name: "should return configuration of github retriever", + args: []string{ + "--kind", + "github", + "--repository-slug", + "thomaspoignant/go-feature-flag", + "--auth-token", + "XXX_GH_TOKEN", + "--path", + "testdata/flag-config.yaml", + "--branch", + "master", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-github.json", + }, + { + name: "should return configuration of gitlab retriever", + args: []string{ + "--kind", + "gitlab", + "--base-url", + "https://gitlab.com/api/v4/", + "--repository-slug", + "thomaspoignant/go-feature-flag", + "--auth-token", + "XXX_GITLAB_TOKEN", + "--branch", + "master", + "--path", + "testdata/flag-config.yaml", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-gitlab.json", }, } for _, tt := range tests { diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 49b798c7351..60a65ad2c68 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -45,7 +45,7 @@ func Test_evaluate_Evaluate(t *testing.T) { }, nil }, wantErr: assert.Error, - expectedErr: "impossible to initialize the retrievers, please checkMode your configuration: impossible to retrieve the flags, please checkMode your configuration: open testdata/invalid.yaml: no such file or directory", + expectedErr: "impossible to initialize the retrievers, please check your configuration: impossible to retrieve the flags, please check your configuration: open testdata/invalid.yaml: no such file or directory", }, { name: "Should error if no evaluation context provided", @@ -201,7 +201,7 @@ func Test_evaluate_Evaluate(t *testing.T) { r, err := retrieverInit.InitRetriever( &retrieverconf.RetrieverConf{ Kind: "gitlab", - BaseURL: baseURL, + BaseURL: "https://gitlab.com/api/v4/", RepositorySlug: "thomaspoignant/go-feature-flag", AuthToken: "XXX", Path: "testdata/flag-config.yaml"}) @@ -241,7 +241,7 @@ func Test_evaluate_Evaluate(t *testing.T) { r, err := retrieverInit.InitRetriever( &retrieverconf.RetrieverConf{ Kind: "bitbucket", - BaseURL: baseURL, + BaseURL: "https://gitlab.com/api/v4/", RepositorySlug: "thomaspoignant/go-feature-flag", AuthToken: "XXX", Path: "testdata/flag-config.yaml"}) diff --git a/cmd/cli/evaluate/testdata/res/check-github.json b/cmd/cli/evaluate/testdata/res/check-github.json new file mode 100644 index 00000000000..a86b6b1056f --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-github.json @@ -0,0 +1,30 @@ +{ + "Kind" : "github", + "RepositorySlug" : "thomaspoignant/go-feature-flag", + "Branch" : "master", + "Path" : "testdata/flag-config.yaml", + "GithubToken" : "", + "URL" : "", + "Timeout" : 0, + "HTTPMethod" : "GET", + "HTTPBody" : "", + "HTTPHeaders" : { }, + "Bucket" : "", + "Object" : "", + "Item" : "", + "Namespace" : "", + "ConfigMap" : "", + "Key" : "", + "BaseURL" : "", + "AuthToken" : "XXX_GH_TOKEN", + "URI" : "", + "Table" : "", + "Columns" : null, + "Database" : "", + "Collection" : "", + "RedisOptions" : null, + "RedisPrefix" : "", + "AccountName" : "", + "AccountKey" : "", + "Container" : "" +} \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-gitlab.json b/cmd/cli/evaluate/testdata/res/check-gitlab.json new file mode 100644 index 00000000000..ce025161b20 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-gitlab.json @@ -0,0 +1,30 @@ +{ + "Kind" : "gitlab", + "RepositorySlug" : "thomaspoignant/go-feature-flag", + "Branch" : "master", + "Path" : "testdata/flag-config.yaml", + "GithubToken" : "", + "URL" : "", + "Timeout" : 0, + "HTTPMethod" : "GET", + "HTTPBody" : "", + "HTTPHeaders" : { }, + "Bucket" : "", + "Object" : "", + "Item" : "", + "Namespace" : "", + "ConfigMap" : "", + "Key" : "", + "BaseURL" : "https://gitlab.com/api/v4/", + "AuthToken" : "XXX_GITLAB_TOKEN", + "URI" : "", + "Table" : "", + "Columns" : null, + "Database" : "", + "Collection" : "", + "RedisOptions" : null, + "RedisPrefix" : "", + "AccountName" : "", + "AccountKey" : "", + "Container" : "" +} \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check.json b/cmd/cli/evaluate/testdata/res/check-http.json similarity index 100% rename from cmd/cli/evaluate/testdata/res/check.json rename to cmd/cli/evaluate/testdata/res/check-http.json From f551f0d790df86017dfb3a4a0581abc55b195032 Mon Sep 17 00:00:00 2001 From: deamondev Date: Thu, 2 Oct 2025 18:36:49 +0200 Subject: [PATCH 12/29] add more tests for s3 and bitbucket Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd_test.go | 52 +++++++++++++++++-- cmd/cli/evaluate/evaluate_test.go | 2 +- .../testdata/res/check-bitbucket.json | 30 +++++++++++ cmd/cli/evaluate/testdata/res/check-s3.json | 30 +++++++++++ 4 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/res/check-bitbucket.json create mode 100644 cmd/cli/evaluate/testdata/res/check-s3.json diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 64cdd047b2c..a25db3bf37a 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -109,7 +109,7 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.Error, }, { - name: "should return configuration of http retriever with headers set properly", + name: "should return configuration of http retriever with headers set properly when using check-mode", args: []string{ "--kind", "http", @@ -133,7 +133,7 @@ func TestCmdEvaluate(t *testing.T) { expectedResult: "testdata/res/check-http.json", }, { - name: "should return configuration of github retriever", + name: "should return configuration of github retriever when using check-mode", args: []string{ "--kind", "github", @@ -157,7 +157,7 @@ func TestCmdEvaluate(t *testing.T) { expectedResult: "testdata/res/check-github.json", }, { - name: "should return configuration of gitlab retriever", + name: "should return configuration of gitlab retriever when using check-mode", args: []string{ "--kind", "gitlab", @@ -182,6 +182,52 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-gitlab.json", }, + { + name: "should return configuration of bitbucket retriever when using check-mode", + args: []string{ + "--kind", + "bitbucket", + "--base-url", + "https://bitbucket.com/api/v4/", + "--repository-slug", + "thomaspoignant/go-feature-flag", + "--auth-token", + "XXX_BITBUCKET_TOKEN", + "--branch", + "master", + "--path", + "testdata/flag-config.yaml", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-bitbucket.json", + }, + { + name: "should return configuration of s3 retriever when using check-mode", + args: []string{ + "--kind", + "s3", + "--bucket", + "Bucket", + "--item", + "valid", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-s3.json", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 60a65ad2c68..df9f3bc09a8 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -241,7 +241,7 @@ func Test_evaluate_Evaluate(t *testing.T) { r, err := retrieverInit.InitRetriever( &retrieverconf.RetrieverConf{ Kind: "bitbucket", - BaseURL: "https://gitlab.com/api/v4/", + BaseURL: "https://bitbucket.com/api/v4/", RepositorySlug: "thomaspoignant/go-feature-flag", AuthToken: "XXX", Path: "testdata/flag-config.yaml"}) diff --git a/cmd/cli/evaluate/testdata/res/check-bitbucket.json b/cmd/cli/evaluate/testdata/res/check-bitbucket.json new file mode 100644 index 00000000000..7e909e4870d --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-bitbucket.json @@ -0,0 +1,30 @@ +{ + "Kind": "bitbucket", + "RepositorySlug": "thomaspoignant/go-feature-flag", + "Branch": "master", + "Path": "testdata/flag-config.yaml", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "", + "ConfigMap": "", + "Key": "", + "BaseURL": "https://bitbucket.com/api/v4/", + "AuthToken": "XXX_BITBUCKET_TOKEN", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-s3.json b/cmd/cli/evaluate/testdata/res/check-s3.json new file mode 100644 index 00000000000..a32281ad863 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-s3.json @@ -0,0 +1,30 @@ +{ + "Kind": "s3", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "Bucket", + "Object": "", + "Item": "valid", + "Namespace": "", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file From 6e5416443d40af5fee67b376b88914240561a253 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 4 Oct 2025 13:55:04 +0200 Subject: [PATCH 13/29] add support for gcs Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 12 +++-- cmd/cli/evaluate/evaluate_cmd_test.go | 20 ++++++++ cmd/cli/evaluate/evaluate_test.go | 50 ++++++++++++++++++ cmd/cli/evaluate/testdata/res/check-gcs.json | 30 +++++++++++ retriever/gcstorageretriever/retriever.go | 4 ++ .../gcstorageretriever/retriever_test.go | 51 ++----------------- testutils/gcs_mock.go | 51 +++++++++++++++++++ 7 files changed, 167 insertions(+), 51 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/res/check-gcs.json create mode 100644 testutils/gcs_mock.go diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index dcc9dd98821..d5f6df5a23e 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -27,6 +27,7 @@ var ( body string headers []string timeout int64 + object string evalFlag string evalCtx string checkMode bool @@ -71,6 +72,7 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy HTTPMethod: method, HTTPBody: body, HTTPHeaders: parsedHeaders, + Object: object, } err := retrieverConf.IsValid() @@ -112,11 +114,11 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy evaluateCmd.Flags().StringVar(&url, "url", "", "URL of your configuration file on HTTP") evaluateCmd.Flags().StringVar(&method, - "method", "GET", "Method to access your configuration file on HTTP") + "method", "GET", "HTTP method to access your configuration file on HTTP") evaluateCmd.Flags().StringVar(&body, - "body", "", "Body to access your configuration file on HTTP") + "body", "", "Http body to access your configuration file on HTTP") evaluateCmd.Flags().StringArrayVar(&headers, - "header", nil, "Header to access your configuration file on HTTP (may be repeated). See example of `evaluate` command for usages") + "header", nil, "HTTP header to access your configuration file on HTTP (may be repeated). See example of `evaluate` command for usages") evaluateCmd.Flags().Int64Var(&timeout, "timeout", 0, "Timeout in seconds to access your configuration file on HTTP") evaluateCmd.Flags().StringVar(&evalFlag, @@ -124,7 +126,9 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") evaluateCmd.Flags().BoolVar(&checkMode, - "check-mode", false, "Check only mode - it does not perform any evaluation and returns the configuration of spanned retriever") + "check-mode", false, "Check only mode - when set, the command will not perform any evaluation and returns the configuration of spanned retriever") + evaluateCmd.Flags().StringVar(&object, + "object", "", "Object of your configuration file on GCS") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index a25db3bf37a..cd319c76b76 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -228,6 +228,26 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-s3.json", }, + { + name: "should return configuration of GCS retriever when using check-mode", + args: []string{ + "--kind", + "googleStorage", + "--bucket", + "Bucket", + "--object", + "flag-config.yaml", + "--flag", + "test-flag", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-gcs.json", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index df9f3bc09a8..ced95aef9cb 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -10,12 +10,15 @@ import ( retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" "github.com/thomaspoignant/go-feature-flag/model" "github.com/thomaspoignant/go-feature-flag/retriever/bitbucketretriever" + "github.com/thomaspoignant/go-feature-flag/retriever/gcstorageretriever" "github.com/thomaspoignant/go-feature-flag/retriever/githubretriever" "github.com/thomaspoignant/go-feature-flag/retriever/gitlabretriever" "github.com/thomaspoignant/go-feature-flag/retriever/httpretriever" "github.com/thomaspoignant/go-feature-flag/retriever/s3retrieverv2" "github.com/thomaspoignant/go-feature-flag/testutils" "github.com/thomaspoignant/go-feature-flag/testutils/mock" + "golang.org/x/oauth2/google" + "google.golang.org/api/option" ) func Test_evaluate_Evaluate(t *testing.T) { @@ -365,6 +368,53 @@ func Test_evaluate_Evaluate(t *testing.T) { }, }, }, + { + name: "Should evaluate a flag from a GCS", + initEvaluate: func() (evaluate, error) { + mockedStorage := testutils.NewMockedGCS(t) + mockedStorage.WithFiles(t, "flags", map[string]string{"testdata/flag-config.yaml": "flag-config.yaml"}) + + r, err := retrieverInit.InitRetriever( + &retrieverconf.RetrieverConf{ + Kind: "googleStorage", + Bucket: "flags", + Object: "flag-config.yaml", + }) + + if err != nil { + return evaluate{}, err + } + + gcsRetriever, _ := r.(*gcstorageretriever.Retriever) + gcsRetriever.SetOptions([]option.ClientOption{ + option.WithCredentials(&google.Credentials{}), + option.WithHTTPClient(mockedStorage.Server.HTTPClient()), + }) + + return evaluate{ + retriever: r, + fileFormat: "yaml", + flag: "test-flag", + evaluationCtx: `{"targetingKey": "user-123"}`, + }, nil + }, + wantErr: assert.NoError, + expectedResult: map[string]model.RawVarResult{ + "test-flag": { + TrackEvents: true, + VariationType: "Default", + Failed: false, + Version: "", + Reason: "DEFAULT", + ErrorCode: "", + ErrorDetails: "", + Value: false, + Cacheable: true, + Metadata: map[string]interface{}{"description": "this is a simple feature flag", + "issue-link": "https://jira.xxx/GOFF-01"}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/testdata/res/check-gcs.json b/cmd/cli/evaluate/testdata/res/check-gcs.json new file mode 100644 index 00000000000..56a1e16d730 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-gcs.json @@ -0,0 +1,30 @@ +{ + "Kind": "googleStorage", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "Bucket", + "Object": "flag-config.yaml", + "Item": "", + "Namespace": "", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file diff --git a/retriever/gcstorageretriever/retriever.go b/retriever/gcstorageretriever/retriever.go index 53e6f20644f..8f6de0b0a70 100644 --- a/retriever/gcstorageretriever/retriever.go +++ b/retriever/gcstorageretriever/retriever.go @@ -33,6 +33,10 @@ type Retriever struct { obj *storage.ObjectHandle } +func (retriever *Retriever) SetOptions(options []option.ClientOption) { + retriever.Options = options +} + // Retrieve is the function in charge of fetching the flag configuration. func (retriever *Retriever) Retrieve(ctx context.Context) (content []byte, err error) { if retriever.obj == nil { diff --git a/retriever/gcstorageretriever/retriever_test.go b/retriever/gcstorageretriever/retriever_test.go index 1648ba0ffeb..e5864c40049 100644 --- a/retriever/gcstorageretriever/retriever_test.go +++ b/retriever/gcstorageretriever/retriever_test.go @@ -2,13 +2,11 @@ package gcstorageretriever import ( "context" - "crypto/md5" //nolint: gosec - "encoding/base64" "os" "testing" - "github.com/fsouza/fake-gcs-server/fakestorage" "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/testutils" "golang.org/x/oauth2/google" "google.golang.org/api/option" ) @@ -99,8 +97,8 @@ func TestRetriever_Retrieve(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - mockedStorage := newMockedGCS(t) - mockedStorage.withFiles(t, tt.storage.bucket, tt.storage.files) + mockedStorage := testutils.NewMockedGCS(t) + mockedStorage.WithFiles(t, tt.storage.bucket, tt.storage.files) retriever := &Retriever{ Bucket: tt.fields.Bucket, @@ -144,50 +142,9 @@ func TestRetriever_Retrieve(t *testing.T) { } if tt.wantUpdated { - mockedStorage.withFiles(t, tt.storage.bucket, tt.storage.updatedFiles) + mockedStorage.WithFiles(t, tt.storage.bucket, tt.storage.updatedFiles) assertRetrieve(tt.wantWhenUpdated) } }) } } - -type mockedStorage struct { - Server *fakestorage.Server -} - -func newMockedGCS(t *testing.T) mockedStorage { - server := fakestorage.NewServer(nil) - t.Cleanup(func() { - server.Stop() - }) - - return mockedStorage{ - Server: server, - } -} - -func (m mockedStorage) withFiles(t *testing.T, bucketName string, files map[string]string) { - for filename, name := range files { - content, err := os.ReadFile(filename) - if err != nil { - t.Fatalf("could not read testfile: %v", err) - } - - object := fakestorage.Object{ - Content: content, - ObjectAttrs: fakestorage.ObjectAttrs{ - BucketName: bucketName, - Name: name, - Md5Hash: encodedMd5Hash(content), - }, - } - m.Server.CreateObject(object) - } -} - -func encodedMd5Hash(content []byte) string { - h := md5.New() //nolint: gosec - h.Write(content) - - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} diff --git a/testutils/gcs_mock.go b/testutils/gcs_mock.go new file mode 100644 index 00000000000..a4e14e0c303 --- /dev/null +++ b/testutils/gcs_mock.go @@ -0,0 +1,51 @@ +package testutils + +import ( + "crypto/md5" + "encoding/base64" + "os" + "testing" + + "github.com/fsouza/fake-gcs-server/fakestorage" +) + +type MockedStorage struct { + Server *fakestorage.Server +} + +func NewMockedGCS(t *testing.T) MockedStorage { + server := fakestorage.NewServer(nil) + t.Cleanup(func() { + server.Stop() + }) + + return MockedStorage{ + Server: server, + } +} + +func (m MockedStorage) WithFiles(t *testing.T, bucketName string, files map[string]string) { + for filename, name := range files { + content, err := os.ReadFile(filename) + if err != nil { + t.Fatalf("could not read testfile: %v", err) + } + + object := fakestorage.Object{ + Content: content, + ObjectAttrs: fakestorage.ObjectAttrs{ + BucketName: bucketName, + Name: name, + Md5Hash: EncodedMd5Hash(content), + }, + } + m.Server.CreateObject(object) + } +} + +func EncodedMd5Hash(content []byte) string { + h := md5.New() //nolint: gosec + h.Write(content) + + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} From 2d977609022e9605dfb996464b15f10ed04f43d0 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 4 Oct 2025 14:41:23 +0200 Subject: [PATCH 14/29] little refactor Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 12 ++++ cmd/cli/evaluate/evaluate_test.go | 12 ++-- .../testdata/res/check-bitbucket.json | 2 +- cmd/cli/evaluate/testdata/res/check-gcs.json | 2 +- .../evaluate/testdata/res/check-github.json | 56 +++++++++---------- .../evaluate/testdata/res/check-gitlab.json | 56 +++++++++---------- cmd/cli/evaluate/testdata/res/check-http.json | 2 +- cmd/cli/evaluate/testdata/res/check-s3.json | 2 +- 8 files changed, 78 insertions(+), 66 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index d5f6df5a23e..ea4f880c4ab 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -28,6 +28,9 @@ var ( headers []string timeout int64 object string + namespace string + configMap string + key string evalFlag string evalCtx string checkMode bool @@ -73,6 +76,9 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy HTTPBody: body, HTTPHeaders: parsedHeaders, Object: object, + Namespace: namespace, + ConfigMap: configMap, + Key: key, } err := retrieverConf.IsValid() @@ -129,6 +135,12 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy "check-mode", false, "Check only mode - when set, the command will not perform any evaluation and returns the configuration of spanned retriever") evaluateCmd.Flags().StringVar(&object, "object", "", "Object of your configuration file on GCS") + evaluateCmd.Flags().StringVar(&namespace, + "namespace", "default", "Namespace of your configuration file on K8s") + evaluateCmd.Flags().StringVar(&configMap, + "config-map", "", "Config map of your configuration file on K8s") + evaluateCmd.Flags().StringVar(&key, + "key", "", "Key of your configuration file on K8s") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index ced95aef9cb..9c20d0d5352 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -176,7 +176,7 @@ func Test_evaluate_Evaluate(t *testing.T) { gitHubRetriever.SetHTTPClient(&mock.HTTP{}) return evaluate{ - retriever: r, + retriever: gitHubRetriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -216,7 +216,7 @@ func Test_evaluate_Evaluate(t *testing.T) { gitLabRetriever.SetHTTPClient(&mock.HTTP{}) return evaluate{ - retriever: r, + retriever: gitLabRetriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -256,7 +256,7 @@ func Test_evaluate_Evaluate(t *testing.T) { bitBucketRetriever.SetHTTPClient(&mock.HTTP{}) return evaluate{ - retriever: r, + retriever: bitBucketRetriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -302,7 +302,7 @@ func Test_evaluate_Evaluate(t *testing.T) { _ = s3Retriever.Init(context.Background(), nil) return evaluate{ - retriever: r, + retriever: s3Retriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -346,7 +346,7 @@ func Test_evaluate_Evaluate(t *testing.T) { httpRetriever.SetHTTPClient(&mock.HTTP{}) return evaluate{ - retriever: r, + retriever: httpRetriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, @@ -392,7 +392,7 @@ func Test_evaluate_Evaluate(t *testing.T) { }) return evaluate{ - retriever: r, + retriever: gcsRetriever, fileFormat: "yaml", flag: "test-flag", evaluationCtx: `{"targetingKey": "user-123"}`, diff --git a/cmd/cli/evaluate/testdata/res/check-bitbucket.json b/cmd/cli/evaluate/testdata/res/check-bitbucket.json index 7e909e4870d..ba08522eadf 100644 --- a/cmd/cli/evaluate/testdata/res/check-bitbucket.json +++ b/cmd/cli/evaluate/testdata/res/check-bitbucket.json @@ -12,7 +12,7 @@ "Bucket": "", "Object": "", "Item": "", - "Namespace": "", + "Namespace": "default", "ConfigMap": "", "Key": "", "BaseURL": "https://bitbucket.com/api/v4/", diff --git a/cmd/cli/evaluate/testdata/res/check-gcs.json b/cmd/cli/evaluate/testdata/res/check-gcs.json index 56a1e16d730..22ebd382a10 100644 --- a/cmd/cli/evaluate/testdata/res/check-gcs.json +++ b/cmd/cli/evaluate/testdata/res/check-gcs.json @@ -12,7 +12,7 @@ "Bucket": "Bucket", "Object": "flag-config.yaml", "Item": "", - "Namespace": "", + "Namespace": "default", "ConfigMap": "", "Key": "", "BaseURL": "", diff --git a/cmd/cli/evaluate/testdata/res/check-github.json b/cmd/cli/evaluate/testdata/res/check-github.json index a86b6b1056f..466b82bb110 100644 --- a/cmd/cli/evaluate/testdata/res/check-github.json +++ b/cmd/cli/evaluate/testdata/res/check-github.json @@ -1,30 +1,30 @@ { - "Kind" : "github", - "RepositorySlug" : "thomaspoignant/go-feature-flag", - "Branch" : "master", - "Path" : "testdata/flag-config.yaml", - "GithubToken" : "", - "URL" : "", - "Timeout" : 0, - "HTTPMethod" : "GET", - "HTTPBody" : "", - "HTTPHeaders" : { }, - "Bucket" : "", - "Object" : "", - "Item" : "", - "Namespace" : "", - "ConfigMap" : "", - "Key" : "", - "BaseURL" : "", - "AuthToken" : "XXX_GH_TOKEN", - "URI" : "", - "Table" : "", - "Columns" : null, - "Database" : "", - "Collection" : "", - "RedisOptions" : null, - "RedisPrefix" : "", - "AccountName" : "", - "AccountKey" : "", - "Container" : "" + "Kind": "github", + "RepositorySlug": "thomaspoignant/go-feature-flag", + "Branch": "master", + "Path": "testdata/flag-config.yaml", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "default", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "XXX_GH_TOKEN", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" } \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-gitlab.json b/cmd/cli/evaluate/testdata/res/check-gitlab.json index ce025161b20..e8f695644f5 100644 --- a/cmd/cli/evaluate/testdata/res/check-gitlab.json +++ b/cmd/cli/evaluate/testdata/res/check-gitlab.json @@ -1,30 +1,30 @@ { - "Kind" : "gitlab", - "RepositorySlug" : "thomaspoignant/go-feature-flag", - "Branch" : "master", - "Path" : "testdata/flag-config.yaml", - "GithubToken" : "", - "URL" : "", - "Timeout" : 0, - "HTTPMethod" : "GET", - "HTTPBody" : "", - "HTTPHeaders" : { }, - "Bucket" : "", - "Object" : "", - "Item" : "", - "Namespace" : "", - "ConfigMap" : "", - "Key" : "", - "BaseURL" : "https://gitlab.com/api/v4/", - "AuthToken" : "XXX_GITLAB_TOKEN", - "URI" : "", - "Table" : "", - "Columns" : null, - "Database" : "", - "Collection" : "", - "RedisOptions" : null, - "RedisPrefix" : "", - "AccountName" : "", - "AccountKey" : "", - "Container" : "" + "Kind": "gitlab", + "RepositorySlug": "thomaspoignant/go-feature-flag", + "Branch": "master", + "Path": "testdata/flag-config.yaml", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "default", + "ConfigMap": "", + "Key": "", + "BaseURL": "https://gitlab.com/api/v4/", + "AuthToken": "XXX_GITLAB_TOKEN", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" } \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-http.json b/cmd/cli/evaluate/testdata/res/check-http.json index fb9ee761436..b6d36bbbd5e 100644 --- a/cmd/cli/evaluate/testdata/res/check-http.json +++ b/cmd/cli/evaluate/testdata/res/check-http.json @@ -20,7 +20,7 @@ "Bucket": "", "Object": "", "Item": "", - "Namespace": "", + "Namespace": "default", "ConfigMap": "", "Key": "", "BaseURL": "", diff --git a/cmd/cli/evaluate/testdata/res/check-s3.json b/cmd/cli/evaluate/testdata/res/check-s3.json index a32281ad863..e6f1f33add7 100644 --- a/cmd/cli/evaluate/testdata/res/check-s3.json +++ b/cmd/cli/evaluate/testdata/res/check-s3.json @@ -12,7 +12,7 @@ "Bucket": "Bucket", "Object": "", "Item": "valid", - "Namespace": "", + "Namespace": "default", "ConfigMap": "", "Key": "", "BaseURL": "", From fc3678b53b4d1b702022fe3b8adb22ee3a1f4b42 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 4 Oct 2025 21:17:02 +0200 Subject: [PATCH 15/29] add support for mongodb Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 23 +++++++++++ cmd/cli/evaluate/evaluate_cmd_test.go | 40 +++++++++++++++++++ cmd/cli/evaluate/testdata/res/check-k8s.json | 30 ++++++++++++++ .../evaluate/testdata/res/check-mongodb.json | 30 ++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 cmd/cli/evaluate/testdata/res/check-k8s.json create mode 100644 cmd/cli/evaluate/testdata/res/check-mongodb.json diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index ea4f880c4ab..05e59fd4c6f 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -2,8 +2,10 @@ package evaluate import ( "encoding/json" + "fmt" "strings" + "github.com/redis/go-redis/v9" "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" @@ -31,6 +33,9 @@ var ( namespace string configMap string key string + uri string + database string + collection string evalFlag string evalCtx string checkMode bool @@ -79,6 +84,9 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy Namespace: namespace, ConfigMap: configMap, Key: key, + URI: uri, + Database: database, + Collection: collection, } err := retrieverConf.IsValid() @@ -141,6 +149,12 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy "config-map", "", "Config map of your configuration file on K8s") evaluateCmd.Flags().StringVar(&key, "key", "", "Key of your configuration file on K8s") + evaluateCmd.Flags().StringVar(&uri, + "uri", "", "URI of your configuration file") + evaluateCmd.Flags().StringVar(&database, + "database", "", "Database of your configuration file on MongoDB") + evaluateCmd.Flags().StringVar(&collection, + "collection", "", "Collection of your configuration file on MongoDB") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() @@ -214,3 +228,12 @@ func parseHeaders() map[string][]string { } return result } + +func parseRedisOptions(jsonStr string) (*redis.Options, error) { + var opts redis.Options + if err := json.Unmarshal([]byte(jsonStr), &opts); err != nil { + return nil, fmt.Errorf("failed to parse redis options: %w", err) + } + + return &opts, nil +} diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index cd319c76b76..2ea2e65ace7 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -248,6 +248,46 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-gcs.json", }, + { + name: "should return configuration of GCS retriever when using check-mode", + args: []string{ + "--kind", + "configmap", + "--namespace", + "goff-ns", + "--config-map", + "goff-config-map", + "--key", + "goff-key", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-k8s.json", + }, + { + name: "should return configuration of Mongodb retriever when using check-mode", + args: []string{ + "--kind", + "mongodb", + "--uri", + "mongodb://localhost:27017", + "--collection", + "goff-collection", + "--database", + "goff-db", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-mongodb.json", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/testdata/res/check-k8s.json b/cmd/cli/evaluate/testdata/res/check-k8s.json new file mode 100644 index 00000000000..3b7d3d52e14 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-k8s.json @@ -0,0 +1,30 @@ +{ + "Kind": "configmap", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "goff-ns", + "ConfigMap": "goff-config-map", + "Key": "goff-key", + "BaseURL": "", + "AuthToken": "", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-mongodb.json b/cmd/cli/evaluate/testdata/res/check-mongodb.json new file mode 100644 index 00000000000..3a88033ab66 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-mongodb.json @@ -0,0 +1,30 @@ +{ + "Kind": "mongodb", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "default", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "mongodb://localhost:27017", + "Table": "", + "Columns": null, + "Database": "goff-db", + "Collection": "goff-collection", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file From fadeb128f9fa8d87188ad54d5694685814055935 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 5 Oct 2025 12:54:48 +0200 Subject: [PATCH 16/29] add support for azure and postgres Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 40 +++++++++++++---- cmd/cli/evaluate/evaluate_cmd_test.go | 44 +++++++++++++++++++ .../evaluate/testdata/res/check-azure.json | 30 +++++++++++++ .../evaluate/testdata/res/check-postgres.json | 33 ++++++++++++++ 4 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 cmd/cli/evaluate/testdata/res/check-azure.json create mode 100644 cmd/cli/evaluate/testdata/res/check-postgres.json diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 05e59fd4c6f..b299db393b0 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -2,10 +2,8 @@ package evaluate import ( "encoding/json" - "fmt" "strings" - "github.com/redis/go-redis/v9" "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" @@ -36,6 +34,11 @@ var ( uri string database string collection string + container string + accountName string + accountKey string + table string + columns []string evalFlag string evalCtx string checkMode bool @@ -59,6 +62,7 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy " if no specific flag requested it will evaluate all flags", RunE: func(cmd *cobra.Command, args []string) error { parsedHeaders := parseHeaders() + parsedColumns := parseColumns() retrieverConf := retrieverconf.RetrieverConf{ Kind: retrieverconf.RetrieverKind(kind), @@ -87,6 +91,11 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy URI: uri, Database: database, Collection: collection, + Container: container, + AccountName: accountName, + AccountKey: accountKey, + Table: table, + Columns: parsedColumns, } err := retrieverConf.IsValid() @@ -155,6 +164,16 @@ evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentTy "database", "", "Database of your configuration file on MongoDB") evaluateCmd.Flags().StringVar(&collection, "collection", "", "Collection of your configuration file on MongoDB") + evaluateCmd.Flags().StringVar(&container, + "container", "", "Container of your configuration file on Azure Blob Storage") + evaluateCmd.Flags().StringVar(&accountName, + "account-name", "", "Account name of your configuration file on Azure Blob Storage") + evaluateCmd.Flags().StringVar(&accountKey, + "account-key", "", "Account key of your configuration file on Azure Blob Storage") + evaluateCmd.Flags().StringVar(&table, + "table", "", "Postgres table of your configuration file on Postgres") + evaluateCmd.Flags().StringArrayVar(&columns, + "column", nil, "Postgres column mapping of your configuration file on Postgres (may be repeated)") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") _ = evaluateCmd.Flags() @@ -229,11 +248,16 @@ func parseHeaders() map[string][]string { return result } -func parseRedisOptions(jsonStr string) (*redis.Options, error) { - var opts redis.Options - if err := json.Unmarshal([]byte(jsonStr), &opts); err != nil { - return nil, fmt.Errorf("failed to parse redis options: %w", err) +func parseColumns() map[string]string { + result := make(map[string]string) + for _, c := range columns { + parts := strings.SplitN(c, ":", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + result[key] = val } - - return &opts, nil + return result } diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 2ea2e65ace7..97d5d3df815 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -288,6 +288,50 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-mongodb.json", }, + { + name: "should return configuration of Azure Blob Storage retriever when using check-mode", + args: []string{ + "--kind", + "azureBlobStorage", + "--container", + "goff-container", + "--account-name", + "goff-user", + "--account-key", + "goff-key", + "--object", + "goff-object", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-azure.json", + }, + { + name: "should return configuration of Postgres retriever when using check-mode", + args: []string{ + "--kind", + "postgresql", + "--uri", + "postgresql://localhost:5432", + "--table", + "goff-table", + "--column", + "flag_name: nonexistentcolumn", + "--column", + "config: config", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + "--check-mode", + }, + wantErr: assert.NoError, + expectedResult: "testdata/res/check-postgres.json", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/cli/evaluate/testdata/res/check-azure.json b/cmd/cli/evaluate/testdata/res/check-azure.json new file mode 100644 index 00000000000..2929fe96646 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-azure.json @@ -0,0 +1,30 @@ +{ + "Kind": "azureBlobStorage", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "goff-object", + "Item": "", + "Namespace": "default", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "", + "Table": "", + "Columns": null, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "goff-user", + "AccountKey": "goff-key", + "Container": "goff-container" +} \ No newline at end of file diff --git a/cmd/cli/evaluate/testdata/res/check-postgres.json b/cmd/cli/evaluate/testdata/res/check-postgres.json new file mode 100644 index 00000000000..3f22e7309f4 --- /dev/null +++ b/cmd/cli/evaluate/testdata/res/check-postgres.json @@ -0,0 +1,33 @@ +{ + "Kind": "postgresql", + "RepositorySlug": "", + "Branch": "", + "Path": "", + "GithubToken": "", + "URL": "", + "Timeout": 0, + "HTTPMethod": "GET", + "HTTPBody": "", + "HTTPHeaders": {}, + "Bucket": "", + "Object": "", + "Item": "", + "Namespace": "default", + "ConfigMap": "", + "Key": "", + "BaseURL": "", + "AuthToken": "", + "URI": "postgresql://localhost:5432", + "Table": "goff-table", + "Columns": { + "config": "config", + "flag_name": "nonexistentcolumn" + }, + "Database": "", + "Collection": "", + "RedisOptions": null, + "RedisPrefix": "", + "AccountName": "", + "AccountKey": "", + "Container": "" +} \ No newline at end of file From 8230bce2571563ea1d862556f11cc08cd17436ff Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 5 Oct 2025 13:18:54 +0200 Subject: [PATCH 17/29] start writing docs Signed-off-by: deamondev --- cmd/cli/README.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 274e3ea6065..336112e7f2f 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -4,40 +4,70 @@ The GO Feature Flag Command Line is a CLI tool to interact with GO Feature Flag in your terminal. For now it supports the following commands: + - `evaluate` to evaluate feature flags directly in your terminal - `lint` to validate a configuration file format. ## How to install the cli ### Install using Homebrew (mac and linux) + ```shell brew tap thomaspoignant/homebrew-tap brew install go-feature-flag-cli ``` ### Install using docker + ```shell docker pull gofeatureflag/go-feature-flag-cli ``` -More information about the usage of the container in the [dockerhub page](https://hub.docker.com/r/gofeatureflag/go-feature-flag-cli). + +More information about the usage of the container in +the [dockerhub page](https://hub.docker.com/r/gofeatureflag/go-feature-flag-cli). # How to use the command line **`go-feature-flag-cli`** is a command line tool. ## How to evaluate a flag + +The evaluate command allows you to evaluate a feature flag or inspect the configuration of your retriever using +`--check-mode` + +```shell +go-feature-flag-cli evaluate [OPTIONS] +``` + +### Key Flags + +| Flag | Shorthand | Description | Default | +|----------------|-----------|---------------------------------------------------------------------------------------------------------------------------------|---------| +| `--kind` | k | Kind of configuration source | file | +| `--config` | c | Path to the local flag configuration file | "" | +| `--path` | p | Path to the local or remote flag configuration file | "" | +| `--format` | f | Format of your input file (YAML, JSON or TOML) | yaml | +| `--flag` | | Name of the flag to evaluate | "" | +| `--ctx` | | Evaluation context as a json string | {} | +| `--check-mode` | | Check only mode - when set, the command will not perform any
evaluation and returns the configuration of spanned retriever | false | + ```shell go-feature-flag-cli evaluate --config="" --flag="" --ctx='' ``` ## How to lint a configuration file + ```shell go-feature-flag-cli lint --format="" ``` # License -View [license](https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE) information for the software contained in this image. +View [license](https://github.com/thomaspoignant/go-feature-flag/blob/main/LICENSE) information for the software +contained in this image. ## How can I contribute? -This project is open for contribution, see the [contributor's guide](https://github.com/thomaspoignant/go-feature-flag/blob/main/CONTRIBUTING.md) for some helpful tips. + +This project is open for contribution, see +the [contributor's guide](https://github.com/thomaspoignant/go-feature-flag/blob/main/CONTRIBUTING.md) for some helpful +tips. From d09c14fa73523895041f615da70a5806e2440975 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sun, 5 Oct 2025 19:02:28 +0200 Subject: [PATCH 18/29] add readme Signed-off-by: deamondev --- cmd/cli/README.md | 127 +++++++++++++++++- cmd/cli/evaluate/evaluate_cmd.go | 35 ++++- .../evaluate/testdata/res/check-azure.json | 2 +- .../testdata/res/check-bitbucket.json | 2 +- cmd/cli/evaluate/testdata/res/check-gcs.json | 2 +- .../evaluate/testdata/res/check-github.json | 2 +- .../evaluate/testdata/res/check-gitlab.json | 2 +- cmd/cli/evaluate/testdata/res/check-http.json | 2 +- cmd/cli/evaluate/testdata/res/check-k8s.json | 2 +- .../evaluate/testdata/res/check-mongodb.json | 2 +- cmd/cli/evaluate/testdata/res/check-s3.json | 2 +- 11 files changed, 164 insertions(+), 16 deletions(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 336112e7f2f..2dc0f57da22 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -43,18 +43,139 @@ go-feature-flag-cli evaluate [OPTIONS] | Flag | Shorthand | Description | Default | |----------------|-----------|---------------------------------------------------------------------------------------------------------------------------------|---------| -| `--kind` | k | Kind of configuration source | file | -| `--config` | c | Path to the local flag configuration file | "" | -| `--path` | p | Path to the local or remote flag configuration file | "" | +| `--kind` | k | Kind of configuration source. Determines where to read your flags from | file | +| `--config` | c | Path to the local flag configuration file (⚠️ deprecated, use `--path` instead) | "" | | `--format` | f | Format of your input file (YAML, JSON or TOML) | yaml | | `--flag` | | Name of the flag to evaluate | "" | | `--ctx` | | Evaluation context as a json string | {} | | `--check-mode` | | Check only mode - when set, the command will not perform any
evaluation and returns the configuration of spanned retriever | false | +Supported values for `--kind` are: + +- `file` +- `http` +- `github` +- `gitlab` +- `s3` +- `googleStorage` +- `configmap` (kubernetes) +- `mongodb` +- `bitbucket` +- `azureBlobStorage` +- `postgresql` + +**Caution**: We do not support `redis` retriever as for now due to: TODO. + +### Retriever specific flags + +The aforementioned `--kind` parameter is used to determine the retriever to use. The semantic meaning of other flags +depends on that one, for +example `--path` parameter is used to specify local file when `--kind` being `file` but when `--kind` being `github` it +is used to specify +the path to the remote file. + +#### File + +| Flag | Description | Default | +|----------|-------------------------------------------|---------| +| `--path` | Path to the local flag configuration file | "" | + +#### HTTP + +| Flag | Description | Default | +|------------|---------------------------------------------------------------------|---------| +| `--url` | URL of the remote flag configuration file | "" | +| `--method` | HTTP method to access your configuration file on HTTP | "" | +| `--body` | Http body to access your configuration file on HTTP | GET | +| `--header` | Header to add to the request. Supported formats are `k:v` and `k=v` | "" | + +#### GitHub + +| Flag | Description | Default | +|---------------------|-------------------------------------------------------------------------------------------------------------------|---------| +| `--repository-slug` | | "" | +| `--branch` | Name of the repository | "" | +| `--auth-token` | Authentication token to access your configuration file | "" | +| `--github-token` | Authentication token to access your configuration file on GitHub
(⚠️ deprecated, use `--auth-token` instead) | "" | +| `--path` | Path to the remote flag configuration file inside github repository | "" | + +#### GitLab + +| Flag | Description | Default | +|---------------------|---------------------------------------------------------------------|---------| +| `--base-url` | Base URL of your configuration file on Gitlab | "" | +| `--repository-slug` | Name of the repository | "" | +| `--branch` | Name of the repository | "" | +| `--path` | Path to the remote flag configuration file inside gitlab repository | "" | + +#### BitBucket + +| Flag | Description | Default | +|---------------------|------------------------------------------------------------------------|---------| +| `--base-url` | Base URL of your configuration file on BitBucket | "" | +| `--repository-slug` | Name of the repository | "" | +| `--branch` | Name of the repository | "" | +| `--path` | Path to the remote flag configuration file inside bitbucket repository | "" | + +#### S3 + +| Flag | Description | Default | +|------------|--------------------|---------| +| `--bucket` | Name of the bucket | "" | +| `--item` | Item of the bucket | "" | + +#### Google Storage + +| Flag | Description | Default | +|------------|----------------------|---------| +| `--bucket` | Name of the bucket | "" | +| `--object` | Object of the bucket | "" | + +#### ConfigMap (Kubernetes) + +| Flag | Description | Default | +|----------------|----------------------------|-----------| +| `--namespace` | Namespace of the ConfigMap | "default" | +| `--config-map` | Name of the ConfigMap | "" | +| `--key` | Key of the ConfigMap | "" | + +#### MongoDB + +| Flag | Description | Default | +|----------------|-----------------------------------------------------|---------| +| `--uri` | URI of your configuration file | "" | +| `--database` | Database name of your configuration file on mongodb | "" | +| `--collection` | Collection of your configuration file on mongodb | "" | + +#### Azure Blob Storage + +| Flag | Description | Default | +|------------------|-----------------------------|---------| +| `--account-name` | Name of the storage account | "" | +| `--account-key` | Key of the storage account | "" | +| `--container` | Name of the container | "" | +| `--object` | Name of the object blob | "" | + +#### PostgreSQL + +| Flag | Description | Default | +|------------|----------------------------------------------------|---------| +| `--uri` | URI of your configuration file | "" | +| `--table` | Table of your configuration file | "" | +| `--column` | Column mapping to add. Supported format is `c1:c2` | "" | + +As mentioned above the `--config` flag is deprecated and we encourage you to use the `--path` flag instead. For example the following command: + ```shell go-feature-flag-cli evaluate --config="" --flag="" --ctx='' ``` +may be replaced by: + +```shell +go-feature-flag-cli evaluate --kind="file" --path="" --flag="" --ctx='' +``` + ## How to lint a configuration file ```shell diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index b299db393b0..b3c9011c3ab 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -57,12 +57,39 @@ evaluate --kind file --path ./config.yaml --flag flag1 --ctx '{"targetingKey": " # Evaluate a specific flag using http retriever evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentType: application/json' --header 'X-Auth=Token' --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using github retriever +evaluate --kind github --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using gitlab retriever +evaluate --kind gitlab --base-url https://gitlab.com --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using bitbucket retriever +evaluate --kind bitbucket --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using s3 retriever +evaluate --kind s3 --bucket my-bucket --item my-item.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using gcs retriever +evaluate --kind googleStorage --bucket my-bucket --object my-item.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using configmap retriever +evaluate --kind configmap --namespace default --config-map my-configmap --key my-key.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using mongodb retriever +evaluate --kind mongodb --uri mongodb://localhost:27017 --database my-database --collection my-collection --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using azureblob retriever +evaluate --kind azureblob --container my-container --account-name my-account-name --account-key my-account-key --object my-object --flag flag1 --ctx '{"targetingKey": "user-123"}' + +# Evaluate a specific flag using postgres retriever +evaluate --kind postgres --table my-table --column my-column:my-column-type --flag flag1 --ctx '{"targetingKey": "user-123"}' `, Long: "⚙️ Evaluate feature flags based on configuration and context," + " if no specific flag requested it will evaluate all flags", RunE: func(cmd *cobra.Command, args []string) error { - parsedHeaders := parseHeaders() - parsedColumns := parseColumns() + parsedHeaders := parseHTTPHeaders() + parsedColumns := parsePostgresColumns() retrieverConf := retrieverconf.RetrieverConf{ Kind: retrieverconf.RetrieverKind(kind), @@ -231,7 +258,7 @@ func runCheck( return nil } -func parseHeaders() map[string][]string { +func parseHTTPHeaders() map[string][]string { result := make(map[string][]string) for _, h := range headers { parts := strings.SplitN(h, "=", 2) @@ -248,7 +275,7 @@ func parseHeaders() map[string][]string { return result } -func parseColumns() map[string]string { +func parsePostgresColumns() map[string]string { result := make(map[string]string) for _, c := range columns { parts := strings.SplitN(c, ":", 2) diff --git a/cmd/cli/evaluate/testdata/res/check-azure.json b/cmd/cli/evaluate/testdata/res/check-azure.json index 2929fe96646..e085750ec46 100644 --- a/cmd/cli/evaluate/testdata/res/check-azure.json +++ b/cmd/cli/evaluate/testdata/res/check-azure.json @@ -19,7 +19,7 @@ "AuthToken": "", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-bitbucket.json b/cmd/cli/evaluate/testdata/res/check-bitbucket.json index ba08522eadf..5ea5d057368 100644 --- a/cmd/cli/evaluate/testdata/res/check-bitbucket.json +++ b/cmd/cli/evaluate/testdata/res/check-bitbucket.json @@ -19,7 +19,7 @@ "AuthToken": "XXX_BITBUCKET_TOKEN", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-gcs.json b/cmd/cli/evaluate/testdata/res/check-gcs.json index 22ebd382a10..156a65de3c2 100644 --- a/cmd/cli/evaluate/testdata/res/check-gcs.json +++ b/cmd/cli/evaluate/testdata/res/check-gcs.json @@ -19,7 +19,7 @@ "AuthToken": "", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-github.json b/cmd/cli/evaluate/testdata/res/check-github.json index 466b82bb110..6ee09d6cb51 100644 --- a/cmd/cli/evaluate/testdata/res/check-github.json +++ b/cmd/cli/evaluate/testdata/res/check-github.json @@ -19,7 +19,7 @@ "AuthToken": "XXX_GH_TOKEN", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-gitlab.json b/cmd/cli/evaluate/testdata/res/check-gitlab.json index e8f695644f5..bfc1fe32732 100644 --- a/cmd/cli/evaluate/testdata/res/check-gitlab.json +++ b/cmd/cli/evaluate/testdata/res/check-gitlab.json @@ -19,7 +19,7 @@ "AuthToken": "XXX_GITLAB_TOKEN", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-http.json b/cmd/cli/evaluate/testdata/res/check-http.json index b6d36bbbd5e..963a5217f70 100644 --- a/cmd/cli/evaluate/testdata/res/check-http.json +++ b/cmd/cli/evaluate/testdata/res/check-http.json @@ -27,7 +27,7 @@ "AuthToken": "", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-k8s.json b/cmd/cli/evaluate/testdata/res/check-k8s.json index 3b7d3d52e14..dc5e47a8699 100644 --- a/cmd/cli/evaluate/testdata/res/check-k8s.json +++ b/cmd/cli/evaluate/testdata/res/check-k8s.json @@ -19,7 +19,7 @@ "AuthToken": "", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-mongodb.json b/cmd/cli/evaluate/testdata/res/check-mongodb.json index 3a88033ab66..07e17ece750 100644 --- a/cmd/cli/evaluate/testdata/res/check-mongodb.json +++ b/cmd/cli/evaluate/testdata/res/check-mongodb.json @@ -19,7 +19,7 @@ "AuthToken": "", "URI": "mongodb://localhost:27017", "Table": "", - "Columns": null, + "Columns": {}, "Database": "goff-db", "Collection": "goff-collection", "RedisOptions": null, diff --git a/cmd/cli/evaluate/testdata/res/check-s3.json b/cmd/cli/evaluate/testdata/res/check-s3.json index e6f1f33add7..72c43720fc2 100644 --- a/cmd/cli/evaluate/testdata/res/check-s3.json +++ b/cmd/cli/evaluate/testdata/res/check-s3.json @@ -19,7 +19,7 @@ "AuthToken": "", "URI": "", "Table": "", - "Columns": null, + "Columns": {}, "Database": "", "Collection": "", "RedisOptions": null, From 58acc14a475103477b6634cad0f99249f27c825a Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 6 Oct 2025 08:58:04 +0200 Subject: [PATCH 19/29] add timeout in docs Signed-off-by: deamondev --- cmd/cli/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 2dc0f57da22..98ac6f6a138 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -47,6 +47,7 @@ go-feature-flag-cli evaluate [OPTIONS] | `--config` | c | Path to the local flag configuration file (⚠️ deprecated, use `--path` instead) | "" | | `--format` | f | Format of your input file (YAML, JSON or TOML) | yaml | | `--flag` | | Name of the flag to evaluate | "" | +| `--timeout` | | Timeout in seconds to access your configuration file | 0 | | `--ctx` | | Evaluation context as a json string | {} | | `--check-mode` | | Check only mode - when set, the command will not perform any
evaluation and returns the configuration of spanned retriever | false | @@ -164,7 +165,8 @@ the path to the remote file. | `--table` | Table of your configuration file | "" | | `--column` | Column mapping to add. Supported format is `c1:c2` | "" | -As mentioned above the `--config` flag is deprecated and we encourage you to use the `--path` flag instead. For example the following command: +As mentioned above the `--config` flag is deprecated and we encourage you to use the `--path` flag instead. For example +the following command: ```shell go-feature-flag-cli evaluate --config="" --flag="" --ctx='' From e0fb0ca2677b56d62d4ea362ff2cb8bc9ab81758 Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 6 Oct 2025 09:02:32 +0200 Subject: [PATCH 20/29] link redis options issue Signed-off-by: deamondev --- cmd/cli/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 98ac6f6a138..82994a205c2 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -65,7 +65,7 @@ Supported values for `--kind` are: - `azureBlobStorage` - `postgresql` -**Caution**: We do not support `redis` retriever as for now due to: TODO. +**Caution**: We do not support `redis` retriever as for now due to: https://github.com/thomaspoignant/go-feature-flag/issues/4023. ### Retriever specific flags From fcf358d7f9cc1e3f8a4460ada3fdf06bd2e94965 Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 6 Oct 2025 09:12:25 +0200 Subject: [PATCH 21/29] add todo comment Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index b3c9011c3ab..48138f30aa9 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -221,6 +221,8 @@ func runEvaluate( return err } + //TODO: Depending on https://github.com/thomaspoignant/go-feature-flag/issues/4024 we should refine type of retriever and call its init method + e := evaluate{ retriever: r, fileFormat: flagFormat, From 8e94cb15dcd47a470d0254c47b56440bb78a146f Mon Sep 17 00:00:00 2001 From: deamondev Date: Fri, 17 Oct 2025 14:19:46 +0200 Subject: [PATCH 22/29] little refactor Signed-off-by: deamondev --- cmd/cli/README.md | 7 ++++--- cmd/cli/evaluate/evaluate_cmd.go | 5 ++--- cmd/cli/evaluate/evaluate_cmd_test.go | 2 +- cmd/cli/evaluate/evaluate_test.go | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index 82994a205c2..fdd7249d815 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -65,7 +65,8 @@ Supported values for `--kind` are: - `azureBlobStorage` - `postgresql` -**Caution**: We do not support `redis` retriever as for now due to: https://github.com/thomaspoignant/go-feature-flag/issues/4023. +**Caution**: We do not support `redis` retriever as for now due +to: https://github.com/thomaspoignant/go-feature-flag/issues/4023. ### Retriever specific flags @@ -86,8 +87,8 @@ the path to the remote file. | Flag | Description | Default | |------------|---------------------------------------------------------------------|---------| | `--url` | URL of the remote flag configuration file | "" | -| `--method` | HTTP method to access your configuration file on HTTP | "" | -| `--body` | Http body to access your configuration file on HTTP | GET | +| `--method` | HTTP method to access your configuration file on HTTP | GET | +| `--body` | Http body to access your configuration file on HTTP | "" | | `--header` | Header to add to the request. Supported formats are `k:v` and `k=v` | "" | #### GitHub diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 48138f30aa9..0a4833dd903 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -203,7 +203,6 @@ evaluate --kind postgres --table my-table --column my-column:my-column-type --fl "column", nil, "Postgres column mapping of your configuration file on Postgres (may be repeated)") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") - _ = evaluateCmd.Flags() return evaluateCmd } @@ -263,9 +262,9 @@ func runCheck( func parseHTTPHeaders() map[string][]string { result := make(map[string][]string) for _, h := range headers { - parts := strings.SplitN(h, "=", 2) + parts := strings.SplitN(h, ":", 2) if len(parts) != 2 { - parts = strings.SplitN(h, ":", 2) + parts = strings.SplitN(h, "=", 2) } if len(parts) != 2 { continue diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 97d5d3df815..9c713794c6d 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -249,7 +249,7 @@ func TestCmdEvaluate(t *testing.T) { expectedResult: "testdata/res/check-gcs.json", }, { - name: "should return configuration of GCS retriever when using check-mode", + name: "should return configuration of k8s retriever when using check-mode", args: []string{ "--kind", "configmap", diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 67b533d8b67..8f8bc4084bb 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -24,7 +24,6 @@ import ( func Test_evaluate_Evaluate(t *testing.T) { tests := []struct { name string - evaluate evaluate initEvaluate func() (evaluate, error) wantErr assert.ErrorAssertionFunc expectedErr string From 76f0a13917ddf5e7b65e237b6eb83587986402f8 Mon Sep 17 00:00:00 2001 From: deamondev Date: Fri, 17 Oct 2025 15:08:08 +0200 Subject: [PATCH 23/29] trailing space Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 44 +++++++++++------ cmdhelpers/retrieverconf/retriever_conf.go | 57 +++++++++++----------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 0a4833dd903..305906eddd0 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -44,6 +44,7 @@ var ( checkMode bool ) +// nolint:funlen func NewEvaluateCmd() *cobra.Command { evaluateCmd := &cobra.Command{ Use: "evaluate", @@ -56,34 +57,43 @@ evaluate --config ./config.yaml --flag flag1 --ctx '{"targetingKey": "user-123"} evaluate --kind file --path ./config.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using http retriever -evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentType: application/json' --header 'X-Auth=Token' --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind http --url http://localhost:8080/config.yaml --header 'ContentType: application/json' --header +'X-Auth=Token' --flag flag1 --ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using github retriever -evaluate --kind github --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind github --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 +--ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using gitlab retriever -evaluate --kind gitlab --base-url https://gitlab.com --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind gitlab --base-url https://gitlab.com --repository-slug thomaspoignant/go-feature-flag +--branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using bitbucket retriever -evaluate --kind bitbucket --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind bitbucket --repository-slug thomaspoignant/go-feature-flag --branch master --flag flag1 +--ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using s3 retriever evaluate --kind s3 --bucket my-bucket --item my-item.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using gcs retriever -evaluate --kind googleStorage --bucket my-bucket --object my-item.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind googleStorage --bucket my-bucket --object my-item.yaml --flag flag1 --ctx +'{"targetingKey": "user-123"}' # Evaluate a specific flag using configmap retriever -evaluate --kind configmap --namespace default --config-map my-configmap --key my-key.yaml --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind configmap --namespace default --config-map my-configmap --key my-key.yaml --flag flag1 +--ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using mongodb retriever -evaluate --kind mongodb --uri mongodb://localhost:27017 --database my-database --collection my-collection --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind mongodb --uri mongodb://localhost:27017 --database my-database --collection my-collection --flag flag1 +--ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using azureblob retriever -evaluate --kind azureblob --container my-container --account-name my-account-name --account-key my-account-key --object my-object --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind azureblob --container my-container --account-name my-account-name --account-key my-account-key +--object my-object --flag flag1 --ctx '{"targetingKey": "user-123"}' # Evaluate a specific flag using postgres retriever -evaluate --kind postgres --table my-table --column my-column:my-column-type --flag flag1 --ctx '{"targetingKey": "user-123"}' +evaluate --kind postgres --table my-table --column my-column:my-column-type --flag flag1 +--ctx '{"targetingKey": "user-123"}' `, Long: "⚙️ Evaluate feature flags based on configuration and context," + " if no specific flag requested it will evaluate all flags", @@ -168,15 +178,20 @@ evaluate --kind postgres --table my-table --column my-column:my-column-type --fl evaluateCmd.Flags().StringVar(&body, "body", "", "Http body to access your configuration file on HTTP") evaluateCmd.Flags().StringArrayVar(&headers, - "header", nil, "HTTP header to access your configuration file on HTTP (may be repeated). See example of `evaluate` command for usages") + "header", nil, + "HTTP header to access your configuration file on HTTP (may be repeated). "+ + "See example of `evaluate` command for usages") evaluateCmd.Flags().Int64Var(&timeout, "timeout", 0, "Timeout in seconds to access your configuration file on HTTP") evaluateCmd.Flags().StringVar(&evalFlag, - "flag", "", "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") + "flag", "", + "Name of the flag to evaluate, if empty we will return the evaluation of all the flags") evaluateCmd.Flags().StringVar(&evalCtx, "ctx", "{}", "Evaluation context in JSON format") evaluateCmd.Flags().BoolVar(&checkMode, - "check-mode", false, "Check only mode - when set, the command will not perform any evaluation and returns the configuration of spanned retriever") + "check-mode", false, + "Check only mode - when set, the command will not perform any evaluation and returns "+ + "the configuration of spanned retriever") evaluateCmd.Flags().StringVar(&object, "object", "", "Object of your configuration file on GCS") evaluateCmd.Flags().StringVar(&namespace, @@ -200,7 +215,8 @@ evaluate --kind postgres --table my-table --column my-column:my-column-type --fl evaluateCmd.Flags().StringVar(&table, "table", "", "Postgres table of your configuration file on Postgres") evaluateCmd.Flags().StringArrayVar(&columns, - "column", nil, "Postgres column mapping of your configuration file on Postgres (may be repeated)") + "column", nil, + "Postgres column mapping of your configuration file on Postgres (may be repeated)") _ = evaluateCmd.Flags().MarkDeprecated("github-token", "Use auth-token instead") _ = evaluateCmd.Flags().MarkDeprecated("config", "Use path instead") return evaluateCmd @@ -220,8 +236,6 @@ func runEvaluate( return err } - //TODO: Depending on https://github.com/thomaspoignant/go-feature-flag/issues/4024 we should refine type of retriever and call its init method - e := evaluate{ retriever: r, fileFormat: flagFormat, diff --git a/cmdhelpers/retrieverconf/retriever_conf.go b/cmdhelpers/retrieverconf/retriever_conf.go index cb86661d108..664ac272eb6 100644 --- a/cmdhelpers/retrieverconf/retriever_conf.go +++ b/cmdhelpers/retrieverconf/retriever_conf.go @@ -20,46 +20,45 @@ var DefaultRetrieverConfig = struct { // RetrieverConf contains all the field to configure a retriever type RetrieverConf struct { - Kind RetrieverKind `mapstructure:"kind" koanf:"kind"` - RepositorySlug string `mapstructure:"repositorySlug" koanf:"repositoryslug"` - Branch string `mapstructure:"branch" koanf:"branch"` - Path string `mapstructure:"path" koanf:"path"` + Kind RetrieverKind `mapstructure:"kind" koanf:"kind" json:"Kind"` + RepositorySlug string `mapstructure:"repositorySlug" koanf:"repositoryslug" json:"RepositorySlug"` + Branch string `mapstructure:"branch" koanf:"branch" json:"Branch"` + Path string `mapstructure:"path" koanf:"path" json:"Path"` // Deprecated: Please use AuthToken instead - GithubToken string `mapstructure:"githubToken" koanf:"githubtoken"` - URL string `mapstructure:"url" koanf:"url"` - Timeout int64 `mapstructure:"timeout" koanf:"timeout"` - HTTPMethod string `mapstructure:"method" koanf:"method"` - HTTPBody string `mapstructure:"body" koanf:"body"` - HTTPHeaders map[string][]string `mapstructure:"headers" koanf:"headers"` - Bucket string `mapstructure:"bucket" koanf:"bucket"` - Object string `mapstructure:"object" koanf:"object"` - Item string `mapstructure:"item" koanf:"item"` - Namespace string `mapstructure:"namespace" koanf:"namespace"` - ConfigMap string `mapstructure:"configmap" koanf:"configmap"` - Key string `mapstructure:"key" koanf:"key"` - BaseURL string `mapstructure:"baseUrl" koanf:"baseurl"` - AuthToken string `mapstructure:"token" koanf:"token"` + GithubToken string `mapstructure:"githubToken" koanf:"githubtoken" json:"GithubToken"` + URL string `mapstructure:"url" koanf:"url" json:"URL"` + Timeout int64 `mapstructure:"timeout" koanf:"timeout" json:"Timeout"` + HTTPMethod string `mapstructure:"method" koanf:"method" json:"HTTPMethod"` + HTTPBody string `mapstructure:"body" koanf:"body" json:"HTTPBody"` + HTTPHeaders map[string][]string `mapstructure:"headers" koanf:"headers" json:"HTTPHeaders"` + Bucket string `mapstructure:"bucket" koanf:"bucket" json:"Bucket"` + Object string `mapstructure:"object" koanf:"object" json:"Object"` + Item string `mapstructure:"item" koanf:"item" json:"Item"` + Namespace string `mapstructure:"namespace" koanf:"namespace" json:"Namespace"` + ConfigMap string `mapstructure:"configmap" koanf:"configmap" json:"ConfigMap"` + Key string `mapstructure:"key" koanf:"key" json:"Key"` + BaseURL string `mapstructure:"baseUrl" koanf:"baseurl" json:"BaseURL"` + AuthToken string `mapstructure:"token" koanf:"token" json:"AuthToken"` // URI is used by // - the postgresql retriever // - the mongodb retriever - URI string `mapstructure:"uri" koanf:"uri"` + URI string `mapstructure:"uri" koanf:"uri" json:"URI"` // Table is used by // - the postgresql retriever - Table string `mapstructure:"table" koanf:"table"` + Table string `mapstructure:"table" koanf:"table" json:"Table"` // Columns is used by // - the postgresql retriever (it allows to use custom column names) - Columns map[string]string `mapstructure:"columns" koanf:"columns"` - - Database string `mapstructure:"database" koanf:"database"` - Collection string `mapstructure:"collection" koanf:"collection"` - RedisOptions *redis.Options `mapstructure:"redisOptions" koanf:"redisOptions"` - RedisPrefix string `mapstructure:"redisPrefix" koanf:"redisPrefix"` - AccountName string `mapstructure:"accountName" koanf:"accountname"` - AccountKey string `mapstructure:"accountKey" koanf:"accountkey"` - Container string `mapstructure:"container" koanf:"container"` + Columns map[string]string `mapstructure:"columns" koanf:"columns" json:"Columns"` + Database string `mapstructure:"database" koanf:"database" json:"Database"` + Collection string `mapstructure:"collection" koanf:"collection" json:"Collection"` + RedisOptions *redis.Options `mapstructure:"redisOptions" koanf:"redisOptions" json:"RedisOptions"` + RedisPrefix string `mapstructure:"redisPrefix" koanf:"redisPrefix" json:"RedisPrefix"` + AccountName string `mapstructure:"accountName" koanf:"accountname" json:"AccountName"` + AccountKey string `mapstructure:"accountKey" koanf:"accountkey" json:"AccountKey"` + Container string `mapstructure:"container" koanf:"container" json:"Container"` } // IsValid validate the configuration of the retriever From 51a412bc61687b2da5f1b83f4c34d642cfcac2dc Mon Sep 17 00:00:00 2001 From: deamondev Date: Fri, 17 Oct 2025 15:09:09 +0200 Subject: [PATCH 24/29] make lint happy Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_test.go | 1 - testutils/gcs_mock.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_test.go b/cmd/cli/evaluate/evaluate_test.go index 8f8bc4084bb..e99cbec8bc3 100644 --- a/cmd/cli/evaluate/evaluate_test.go +++ b/cmd/cli/evaluate/evaluate_test.go @@ -327,7 +327,6 @@ func Test_evaluate_Evaluate(t *testing.T) { { name: "Should evaluate a flag from a HTTP endpoint", initEvaluate: func() (evaluate, error) { - r, err := retrieverInit.InitRetriever( &retrieverconf.RetrieverConf{ Kind: "http", diff --git a/testutils/gcs_mock.go b/testutils/gcs_mock.go index a4e14e0c303..53563cbe831 100644 --- a/testutils/gcs_mock.go +++ b/testutils/gcs_mock.go @@ -1,7 +1,7 @@ package testutils import ( - "crypto/md5" + "crypto/md5" //nolint: gosec "encoding/base64" "os" "testing" From 37303f983e85b1804e04473c0385104dd4418106 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 18 Oct 2025 10:33:42 +0200 Subject: [PATCH 25/29] correct readme for cli Signed-off-by: deamondev --- cmd/cli/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/cli/README.md b/cmd/cli/README.md index fdd7249d815..7a84c637a32 100644 --- a/cmd/cli/README.md +++ b/cmd/cli/README.md @@ -95,8 +95,8 @@ the path to the remote file. | Flag | Description | Default | |---------------------|-------------------------------------------------------------------------------------------------------------------|---------| -| `--repository-slug` | | "" | -| `--branch` | Name of the repository | "" | +| `--repository-slug` | Name of the repository | "" | +| `--branch` | Git branch name | "" | | `--auth-token` | Authentication token to access your configuration file | "" | | `--github-token` | Authentication token to access your configuration file on GitHub
(⚠️ deprecated, use `--auth-token` instead) | "" | | `--path` | Path to the remote flag configuration file inside github repository | "" | @@ -107,7 +107,7 @@ the path to the remote file. |---------------------|---------------------------------------------------------------------|---------| | `--base-url` | Base URL of your configuration file on Gitlab | "" | | `--repository-slug` | Name of the repository | "" | -| `--branch` | Name of the repository | "" | +| `--branch` | Git branch name | "" | | `--path` | Path to the remote flag configuration file inside gitlab repository | "" | #### BitBucket @@ -116,7 +116,7 @@ the path to the remote file. |---------------------|------------------------------------------------------------------------|---------| | `--base-url` | Base URL of your configuration file on BitBucket | "" | | `--repository-slug` | Name of the repository | "" | -| `--branch` | Name of the repository | "" | +| `--branch` | Git branch name | "" | | `--path` | Path to the remote flag configuration file inside bitbucket repository | "" | #### S3 From eb47d9ae6cedc557c4e41729a142895fcf0801b1 Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 18 Oct 2025 11:12:29 +0200 Subject: [PATCH 26/29] correct initialization of retrievers Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 305906eddd0..12f56b7cf07 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -1,13 +1,18 @@ package evaluate import ( + "context" "encoding/json" + "log/slog" "strings" "github.com/spf13/cobra" "github.com/thomaspoignant/go-feature-flag/cmd/cli/helper" "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf" retrieverInit "github.com/thomaspoignant/go-feature-flag/cmdhelpers/retrieverconf/init" + "github.com/thomaspoignant/go-feature-flag/retriever" + "github.com/thomaspoignant/go-feature-flag/utils" + "github.com/thomaspoignant/go-feature-flag/utils/fflog" ) var ( @@ -236,6 +241,16 @@ func runEvaluate( return err } + logger := &fflog.FFLogger{LeveledLogger: slog.Default()} + + if err := tryInitializeStandard(context.Background(), r, logger); err != nil { + return err + } + + if err := tryInitializeWithFlagset(context.Background(), r, logger, utils.DefaultFlagSetName); err != nil { + return err + } + e := evaluate{ retriever: r, fileFormat: flagFormat, @@ -273,6 +288,27 @@ func runCheck( return nil } +func tryInitializeStandard(ctx context.Context, r retriever.Retriever, logger *fflog.FFLogger) error { + if r, ok := r.(retriever.InitializableRetriever); ok { + if err := r.Init(ctx, logger); err != nil { + return err + } + } + + return nil +} + +func tryInitializeWithFlagset( + ctx context.Context, r retriever.Retriever, logger *fflog.FFLogger, flagset string) error { + if r, ok := r.(retriever.InitializableRetrieverWithFlagset); ok { + if err := r.Init(ctx, logger, &flagset); err != nil { + return err + } + } + + return nil +} + func parseHTTPHeaders() map[string][]string { result := make(map[string][]string) for _, h := range headers { From a2e0d933ab49cdd1cee9b7729ec50c9e069444bd Mon Sep 17 00:00:00 2001 From: deamondev Date: Sat, 18 Oct 2025 16:47:05 +0200 Subject: [PATCH 27/29] correct tests after adding init calls Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 5 +-- cmd/cli/evaluate/evaluate_cmd_test.go | 45 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 12f56b7cf07..14db9a425fb 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -3,6 +3,7 @@ package evaluate import ( "context" "encoding/json" + "fmt" "log/slog" "strings" @@ -291,7 +292,7 @@ func runCheck( func tryInitializeStandard(ctx context.Context, r retriever.Retriever, logger *fflog.FFLogger) error { if r, ok := r.(retriever.InitializableRetriever); ok { if err := r.Init(ctx, logger); err != nil { - return err + return fmt.Errorf("impossible to init retriever: %v", err) } } @@ -302,7 +303,7 @@ func tryInitializeWithFlagset( ctx context.Context, r retriever.Retriever, logger *fflog.FFLogger, flagset string) error { if r, ok := r.(retriever.InitializableRetrieverWithFlagset); ok { if err := r.Init(ctx, logger, &flagset); err != nil { - return err + return fmt.Errorf("impossible to init flagset retriever: %v", err) } } diff --git a/cmd/cli/evaluate/evaluate_cmd_test.go b/cmd/cli/evaluate/evaluate_cmd_test.go index 9c713794c6d..de9efabdac2 100644 --- a/cmd/cli/evaluate/evaluate_cmd_test.go +++ b/cmd/cli/evaluate/evaluate_cmd_test.go @@ -15,6 +15,7 @@ func TestCmdEvaluate(t *testing.T) { args []string wantErr assert.ErrorAssertionFunc expectedResult string + expectedErr string }{ { name: "should return an error if flag does not exists", @@ -288,6 +289,25 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-mongodb.json", }, + { + name: "should return an initialization error if mongodb uri is invalid", + args: []string{ + "--kind", + "mongodb", + "--uri", + "inv4lid", + "--collection", + "goff-collection", + "--database", + "goff-db", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + }, + wantErr: assert.Error, + expectedErr: "impossible to init retriever: error parsing uri: scheme must be \"mongodb\" or \"mongodb+srv\"", + }, { name: "should return configuration of Azure Blob Storage retriever when using check-mode", args: []string{ @@ -332,6 +352,27 @@ func TestCmdEvaluate(t *testing.T) { wantErr: assert.NoError, expectedResult: "testdata/res/check-postgres.json", }, + { + name: "should return an initialization error if postgres uri is invalid", + args: []string{ + "--kind", + "postgresql", + "--uri", + "inv4lid", + "--table", + "goff-table", + "--column", + "flag_name: nonexistentcolumn", + "--column", + "config: config", + "--ctx", + `{"targetingKey": "user-123"}`, + "--format", + "yaml", + }, + wantErr: assert.Error, + expectedErr: "impossible to init flagset retriever: cannot parse `inv4lid`: failed to parse as keyword/value (invalid keyword/value)", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -357,6 +398,10 @@ func TestCmdEvaluate(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, string(expectedContent), string(gotContent)) } + + if tt.expectedErr != "" { + assert.EqualError(t, err, tt.expectedErr) + } }) } } From 00d99061e455ce504d9833dd59cc64e86ae83e6b Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 27 Oct 2025 15:04:07 +0100 Subject: [PATCH 28/29] remove json tags Signed-off-by: deamondev --- cmdhelpers/retrieverconf/retriever_conf.go | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/cmdhelpers/retrieverconf/retriever_conf.go b/cmdhelpers/retrieverconf/retriever_conf.go index 664ac272eb6..289773c74fe 100644 --- a/cmdhelpers/retrieverconf/retriever_conf.go +++ b/cmdhelpers/retrieverconf/retriever_conf.go @@ -20,45 +20,45 @@ var DefaultRetrieverConfig = struct { // RetrieverConf contains all the field to configure a retriever type RetrieverConf struct { - Kind RetrieverKind `mapstructure:"kind" koanf:"kind" json:"Kind"` - RepositorySlug string `mapstructure:"repositorySlug" koanf:"repositoryslug" json:"RepositorySlug"` - Branch string `mapstructure:"branch" koanf:"branch" json:"Branch"` - Path string `mapstructure:"path" koanf:"path" json:"Path"` + Kind RetrieverKind `mapstructure:"kind" koanf:"kind"` + RepositorySlug string `mapstructure:"repositorySlug" koanf:"repositoryslug"` + Branch string `mapstructure:"branch" koanf:"branch"` + Path string `mapstructure:"path" koanf:"path"` // Deprecated: Please use AuthToken instead - GithubToken string `mapstructure:"githubToken" koanf:"githubtoken" json:"GithubToken"` - URL string `mapstructure:"url" koanf:"url" json:"URL"` - Timeout int64 `mapstructure:"timeout" koanf:"timeout" json:"Timeout"` - HTTPMethod string `mapstructure:"method" koanf:"method" json:"HTTPMethod"` - HTTPBody string `mapstructure:"body" koanf:"body" json:"HTTPBody"` - HTTPHeaders map[string][]string `mapstructure:"headers" koanf:"headers" json:"HTTPHeaders"` - Bucket string `mapstructure:"bucket" koanf:"bucket" json:"Bucket"` - Object string `mapstructure:"object" koanf:"object" json:"Object"` - Item string `mapstructure:"item" koanf:"item" json:"Item"` - Namespace string `mapstructure:"namespace" koanf:"namespace" json:"Namespace"` - ConfigMap string `mapstructure:"configmap" koanf:"configmap" json:"ConfigMap"` - Key string `mapstructure:"key" koanf:"key" json:"Key"` - BaseURL string `mapstructure:"baseUrl" koanf:"baseurl" json:"BaseURL"` - AuthToken string `mapstructure:"token" koanf:"token" json:"AuthToken"` + GithubToken string `mapstructure:"githubToken" koanf:"githubtoken"` + URL string `mapstructure:"url" koanf:"url"` + Timeout int64 `mapstructure:"timeout" koanf:"timeout"` + HTTPMethod string `mapstructure:"method" koanf:"method"` + HTTPBody string `mapstructure:"body" koanf:"body"` + HTTPHeaders map[string][]string `mapstructure:"headers" koanf:"headers"` + Bucket string `mapstructure:"bucket" koanf:"bucket"` + Object string `mapstructure:"object" koanf:"object"` + Item string `mapstructure:"item" koanf:"item"` + Namespace string `mapstructure:"namespace" koanf:"namespace"` + ConfigMap string `mapstructure:"configmap" koanf:"configmap"` + Key string `mapstructure:"key" koanf:"key"` + BaseURL string `mapstructure:"baseUrl" koanf:"baseurl"` + AuthToken string `mapstructure:"token" koanf:"token"` // URI is used by // - the postgresql retriever // - the mongodb retriever - URI string `mapstructure:"uri" koanf:"uri" json:"URI"` + URI string `mapstructure:"uri" koanf:"uri"` // Table is used by // - the postgresql retriever - Table string `mapstructure:"table" koanf:"table" json:"Table"` + Table string `mapstructure:"table" koanf:"table"` // Columns is used by // - the postgresql retriever (it allows to use custom column names) - Columns map[string]string `mapstructure:"columns" koanf:"columns" json:"Columns"` - Database string `mapstructure:"database" koanf:"database" json:"Database"` - Collection string `mapstructure:"collection" koanf:"collection" json:"Collection"` - RedisOptions *redis.Options `mapstructure:"redisOptions" koanf:"redisOptions" json:"RedisOptions"` - RedisPrefix string `mapstructure:"redisPrefix" koanf:"redisPrefix" json:"RedisPrefix"` - AccountName string `mapstructure:"accountName" koanf:"accountname" json:"AccountName"` - AccountKey string `mapstructure:"accountKey" koanf:"accountkey" json:"AccountKey"` - Container string `mapstructure:"container" koanf:"container" json:"Container"` + Columns map[string]string `mapstructure:"columns" koanf:"columns"` + Database string `mapstructure:"database" koanf:"database"` + Collection string `mapstructure:"collection" koanf:"collection"` + RedisOptions *redis.Options `mapstructure:"redisOptions" koanf:"redisOptions"` + RedisPrefix string `mapstructure:"redisPrefix" koanf:"redisPrefix"` + AccountName string `mapstructure:"accountName" koanf:"accountname"` + AccountKey string `mapstructure:"accountKey" koanf:"accountkey"` + Container string `mapstructure:"container" koanf:"container"` } // IsValid validate the configuration of the retriever From 044e22cf304cd22d0449ee142250239c3fd3f969 Mon Sep 17 00:00:00 2001 From: deamondev Date: Mon, 27 Oct 2025 17:03:47 +0100 Subject: [PATCH 29/29] nolint Signed-off-by: deamondev --- cmd/cli/evaluate/evaluate_cmd.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cli/evaluate/evaluate_cmd.go b/cmd/cli/evaluate/evaluate_cmd.go index 14db9a425fb..b236e5a6b81 100644 --- a/cmd/cli/evaluate/evaluate_cmd.go +++ b/cmd/cli/evaluate/evaluate_cmd.go @@ -279,6 +279,7 @@ func runCheck( retrieverConf retrieverconf.RetrieverConf) error { output := helper.Output{} + // nolint:musttag detailed, err := json.MarshalIndent(retrieverConf, "", " ") if err != nil { return err