Repository: datreeio/datree
Pull request #65 (2021-06-14 15:20:21)

feat: config set token
* Added config cmd package to deal with config commands
* Refactored `localConfig`
* Support command `datree config set [key] [value]` - changes key-value in config.yaml


Patch:
diff --git a/Makefile b/Makefile
index 8aaaec5..85245e7 100644
--- a/Makefile
+++ b/Makefile
@@ -12,3 +12,6 @@ create-bin:
 
 print-version:
 	go run -tags=staging -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=0.0.1" main.go version
+
+set-token:
+	go run -tags=staging -ldflags="-X github.com/datreeio/datree/cmd.CliVersion=0.0.1" main.go config set token testtoken
diff --git a/cmd/config/main.go b/cmd/config/main.go
new file mode 100644
index 0000000..f2153df
--- /dev/null
+++ b/cmd/config/main.go
@@ -0,0 +1,39 @@
+package config
+
+import (
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/pkg/localConfig"
+	"github.com/spf13/cobra"
+)
+
+type Messager interface {
+	LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string)
+}
+
+type Printer interface {
+	PrintMessage(messageText string, messageColor string)
+}
+
+type LocalConfig interface {
+	GetLocalConfiguration() (*localConfig.ConfigContent, error)
+	Set(key string, value string) error
+}
+
+type ConfigCommandContext struct {
+	Messager    Messager
+	CliVersion  string
+	Printer     Printer
+	LocalConfig LocalConfig
+}
+
+func New(ctx *ConfigCommandContext) *cobra.Command {
+	configCommand := &cobra.Command{
+		Use:   "config",
+		Short: "Configuration management",
+		Long:  `Internal configuration management for datree config file`,
+	}
+
+	configCommand.AddCommand(NewSetCommand(ctx))
+
+	return configCommand
+}
diff --git a/cmd/config/set.go b/cmd/config/set.go
new file mode 100644
index 0000000..1b224d7
--- /dev/null
+++ b/cmd/config/set.go
@@ -0,0 +1,43 @@
+package config
+
+import (
+	"fmt"
+
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/spf13/cobra"
+)
+
+func NewSetCommand(ctx *ConfigCommandContext) *cobra.Command {
+	setCommand := &cobra.Command{
+		Use:   "set",
+		Short: "Set configuration value",
+		Long:  `Apply value for specific key in datree config.yaml file. Defaults to $HOME/.datree/config.yaml`,
+		Run: func(cmd *cobra.Command, args []string) {
+			messages := make(chan *messager.VersionMessage, 1)
+			go ctx.Messager.LoadVersionMessages(messages, ctx.CliVersion)
+
+			err := set(ctx, args[0], args[1])
+			if err != nil {
+				fmt.Printf("Failed setting %s with value %s. Error: %s", args[0], args[1], err)
+			}
+
+			msg, ok := <-messages
+			if ok {
+				ctx.Printer.PrintMessage(msg.MessageText+"\n", msg.MessageColor)
+			}
+		},
+		Args: cobra.ExactArgs(2),
+	}
+
+	return setCommand
+}
+
+func set(ctx *ConfigCommandContext, key string, value string) error {
+	_, err := ctx.LocalConfig.GetLocalConfiguration()
+	if err != nil {
+		return err
+	}
+
+	err = ctx.LocalConfig.Set(key, value)
+	return err
+}
diff --git a/cmd/root.go b/cmd/root.go
index 4bdcfad..0a4a27c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -4,6 +4,7 @@ import (
 	"github.com/datreeio/datree/bl/evaluation"
 	"github.com/datreeio/datree/bl/messager"
 	"github.com/datreeio/datree/bl/validation"
+	"github.com/datreeio/datree/cmd/config"
 	"github.com/datreeio/datree/cmd/test"
 	"github.com/datreeio/datree/cmd/version"
 	"github.com/datreeio/datree/pkg/cliClient"
@@ -40,6 +41,13 @@ func init() {
 		Messager:   app.context.Messager,
 		Printer:    app.context.Printer,
 	}))
+
+	rootCmd.AddCommand(config.New(&config.ConfigCommandContext{
+		CliVersion:  CliVersion,
+		Messager:    app.context.Messager,
+		Printer:     app.context.Printer,
+		LocalConfig: app.context.LocalConfig,
+	}))
 }
 
 func Execute() error {
@@ -47,7 +55,7 @@ func Execute() error {
 }
 
 type context struct {
-	LocalConfig  *localConfig.LocalConfiguration
+	LocalConfig  *localConfig.LocalConfig
 	Evaluator    *evaluation.Evaluator
 	CliClient    *cliClient.CliClient
 	Messager     *messager.Messager
@@ -61,13 +69,13 @@ type app struct {
 }
 
 func startup() *app {
-	config, _ := localConfig.GetLocalConfiguration()
+	localConfig := localConfig.NewLocalConfig()
 	cliClient := cliClient.NewCliClient(deploymentConfig.URL)
 	printer := printer.CreateNewPrinter()
 
 	return &app{
 		context: &context{
-			LocalConfig:  config,
+			LocalConfig:  localConfig,
 			Evaluator:    evaluation.New(cliClient),
 			CliClient:    cliClient,
 			Messager:     messager.New(cliClient),
diff --git a/pkg/localConfig/localConfig.go b/pkg/localConfig/localConfig.go
index e6912e7..d65b550 100644
--- a/pkg/localConfig/localConfig.go
+++ b/pkg/localConfig/localConfig.go
@@ -9,31 +9,28 @@ import (
 	"github.com/spf13/viper"
 )
 
-type LocalConfiguration struct {
+type ConfigContent struct {
 	CliId string
 }
 
-func GetLocalConfiguration() (*LocalConfiguration, error) {
+type LocalConfig struct {
+}
+
+func NewLocalConfig() *LocalConfig {
+	return &LocalConfig{}
+}
+
+func (lc *LocalConfig) GetLocalConfiguration() (*ConfigContent, error) {
 	viper.SetEnvPrefix("datree")
 	viper.AutomaticEnv()
 	token := viper.GetString("token")
 
 	if token == "" {
-		usr, err := user.Current()
+		configHome, configName, configType, err := setViperConfig()
 		if err != nil {
-			return &LocalConfiguration{}, err
+			return nil, err
 		}
 
-		homedir := usr.HomeDir
-
-		configHome := filepath.Join(homedir, ".datree")
-		configName := "config"
-		configType := "yaml"
-
-		viper.SetConfigName(configName)
-		viper.SetConfigType(configType)
-		viper.AddConfigPath(configHome)
-
 		// workaround for creating config file when not exist
 		// open issue in viper: https://github.com/spf13/viper/issues/430
 		// should be fixed in pr https://github.com/spf13/viper/pull/936
@@ -58,5 +55,52 @@ func GetLocalConfiguration() (*LocalConfiguration, error) {
 		}
 	}
 
-	return &LocalConfiguration{CliId: token}, nil
+	return &ConfigContent{CliId: token}, nil
+}
+
+func (lc *LocalConfig) Set(key string, value string) error {
+	_, _, _, err := setViperConfig()
+	if err != nil {
+		return err
+	}
+
+	viper.Set(key, value)
+	viper.WriteConfig()
+	return nil
+}
+
+func getConfigHome() (string, error) {
+	usr, err := user.Current()
+	if err != nil {
+		return "", err
+	}
+
+	homedir := usr.HomeDir
+	configHome := filepath.Join(homedir, ".datree")
+
+	return configHome, nil
+}
+
+func getConfigName() string {
+	return "config"
+}
+
+func getConfigType() string {
+	return "yaml"
+}
+
+func setViperConfig() (string, string, string, error) {
+	configHome, err := getConfigHome()
+	if err != nil {
+		return "", "", "", nil
+	}
+
+	configName := getConfigName()
+	configType := getConfigType()
+
+	viper.SetConfigName(configName)
+	viper.SetConfigType(configType)
+	viper.AddConfigPath(configHome)
+
+	return configHome, configName, configType, nil
 }


Test patch:
diff --git a/cmd/config/set_test.go b/cmd/config/set_test.go
new file mode 100644
index 0000000..33383da
--- /dev/null
+++ b/cmd/config/set_test.go
@@ -0,0 +1,89 @@
+package config
+
+import (
+	"testing"
+
+	"github.com/datreeio/datree/bl/messager"
+	"github.com/datreeio/datree/pkg/localConfig"
+	"github.com/datreeio/datree/pkg/printer"
+	"github.com/stretchr/testify/mock"
+)
+
+type mockMessager struct {
+	mock.Mock
+}
+
+func (m *mockMessager) LoadVersionMessages(messages chan *messager.VersionMessage, cliVersion string) {
+	go func() {
+		messages <- &messager.VersionMessage{
+			CliVersion:   "1.2.3",
+			MessageText:  "version message mock",
+			MessageColor: "green"}
+		close(messages)
+	}()
+
+	m.Called(messages, cliVersion)
+}
+
+func (m *mockMessager) HandleVersionMessage(messageChannel <-chan *messager.VersionMessage) {
+	m.Called(messageChannel)
+}
+
+type PrinterMock struct {
+	mock.Mock
+}
+
+func (p *PrinterMock) PrintWarnings(warnings []printer.Warning) {
+	p.Called(warnings)
+}
+
+func (p *PrinterMock) PrintSummaryTable(summary printer.Summary) {
+	p.Called(summary)
+}
+
+func (p *PrinterMock) PrintMessage(messageText string, messageColor string) {
+	p.Called(messageText, messageColor)
+}
+
+func (p *PrinterMock) PrintEvaluationSummary(evaluationSummary printer.EvaluationSummary, k8sVersion string) {
+	p.Called(evaluationSummary)
+}
+
+type LocalConfigMock struct {
+	mock.Mock
+}
+
+func (lc *LocalConfigMock) GetLocalConfiguration() (*localConfig.ConfigContent, error) {
+	lc.Called()
+	return &localConfig.ConfigContent{CliId: "previousToken"}, nil
+}
+
+func (lc *LocalConfigMock) Set(key string, value string) error {
+	lc.Called(key, value)
+	return nil
+}
+
+func TestSetCommand(t *testing.T) {
+	messager := &mockMessager{}
+	messager.On("LoadVersionMessages", mock.Anything, mock.Anything)
+
+	printerMock := &PrinterMock{}
+	printerMock.On("PrintWarnings", mock.Anything)
+	printerMock.On("PrintSummaryTable", mock.Anything)
+	printerMock.On("PrintMessage", mock.Anything, mock.Anything)
+	printerMock.On("PrintEvaluationSummary", mock.Anything, mock.Anything)
+
+	localConfigMock := &LocalConfigMock{}
+	localConfigMock.On("GetLocalConfiguration").Return(&localConfig.ConfigContent{CliId: "previousToken"}, nil)
+	localConfigMock.On("Set", mock.Anything, mock.Anything)
+
+	ctx := &ConfigCommandContext{
+		Messager:    messager,
+		CliVersion:  "1.2.3",
+		Printer:     printerMock,
+		LocalConfig: localConfigMock,
+	}
+
+	set(ctx, "testkey", "testvalue")
+	localConfigMock.AssertCalled(t, "Set", "testkey", "testvalue")
+}
diff --git a/cmd/test/main.go b/cmd/test/main.go
index db76ab2..23cbe23 100644
--- a/cmd/test/main.go
+++ b/cmd/test/main.go
@@ -48,9 +48,13 @@ type Reader interface {
 	FilterFiles(paths []string) ([]string, error)
 }
 
+type LocalConfig interface {
+	GetLocalConfiguration() (*localConfig.ConfigContent, error)
+}
+
 type TestCommandContext struct {
 	CliVersion   string
-	LocalConfig  *localConfig.LocalConfiguration
+	LocalConfig  LocalConfig
 	Evaluator    Evaluator
 	Messager     Messager
 	K8sValidator K8sValidator
@@ -89,6 +93,11 @@ func New(ctx *TestCommandContext) *cobra.Command {
 }
 
 func test(ctx *TestCommandContext, paths []string, flags TestCommandFlags) error {
+	localConfigContent, err := ctx.LocalConfig.GetLocalConfiguration()
+	if err != nil {
+		return err
+	}
+
 	isInteractiveMode := (flags.Output != "json") && (flags.Output != "yaml")
 
 	if isInteractiveMode == true {
@@ -124,7 +133,7 @@ func test(ctx *TestCommandContext, paths []string, flags TestCommandFlags) error
 
 	validYamlFilesConfigurationsChan, invalidYamlFilesChan := files.ExtractFilesConfigurations(filesPaths, concurrency)
 
-	createEvaluationResponse, err := ctx.Evaluator.CreateEvaluation(ctx.LocalConfig.CliId, ctx.CliVersion, flags.K8sVersion)
+	createEvaluationResponse, err := ctx.Evaluator.CreateEvaluation(localConfigContent.CliId, ctx.CliVersion, flags.K8sVersion)
 	if err != nil {
 		fmt.Println(err.Error())
 		return err
@@ -187,7 +196,7 @@ func test(ctx *TestCommandContext, paths []string, flags TestCommandFlags) error
 		PassedPolicyCheckCount:    passedPolicyCheckCount,
 	}
 
-	err = evaluation.PrintResults(results, invalidYamlFiles, invalidK8sFiles, evaluationSummary, fmt.Sprintf("https://app.datree.io/login?cliId=%s", ctx.LocalConfig.CliId), flags.Output, ctx.Printer, createEvaluationResponse.K8sVersion)
+	err = evaluation.PrintResults(results, invalidYamlFiles, invalidK8sFiles, evaluationSummary, fmt.Sprintf("https://app.datree.io/login?cliId=%s", localConfigContent.CliId), flags.Output, ctx.Printer, createEvaluationResponse.K8sVersion)
 
 	var invocationFailedErr error = nil
 
diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go
index 1a7dbbc..ae343b1 100644
--- a/cmd/test/main_test.go
+++ b/cmd/test/main_test.go
@@ -99,6 +99,15 @@ func (rm *ReaderMock) FilterFiles(paths []string) ([]string, error) {
 	return args.Get(0).([]string), nil
 }
 
+type LocalConfigMock struct {
+	mock.Mock
+}
+
+func (lc *LocalConfigMock) GetLocalConfiguration() (*localConfig.ConfigContent, error) {
+	lc.Called()
+	return &localConfig.ConfigContent{CliId: "134kh"}, nil
+}
+
 func TestTestCommand(t *testing.T) {
 	evaluationId := 444
 
@@ -136,13 +145,15 @@ func TestTestCommand(t *testing.T) {
 	printerMock.On("PrintEvaluationSummary", mock.Anything, mock.Anything)
 
 	readerMock := &ReaderMock{}
-
 	readerMock.On("FilterFiles", mock.Anything).Return([]string{"file/path"}, nil)
 
+	localConfigMock := &LocalConfigMock{}
+	localConfigMock.On("GetLocalConfiguration").Return(&localConfig.ConfigContent{CliId: "134kh"}, nil)
+
 	ctx := &TestCommandContext{
 		K8sValidator: k8sValidatorMock,
 		Evaluator:    mockedEvaluator,
-		LocalConfig:  &localConfig.LocalConfiguration{CliId: "134kh"},
+		LocalConfig:  localConfigMock,
 		Messager:     messager,
 		Printer:      printerMock,
 		Reader:       readerMock,