Skip to content
This repository was archived by the owner on Oct 10, 2023. It is now read-only.

Commit 701f6e7

Browse files
author
Anuj Chaudhari
committed
Support set/unset environment variables through tanzu config file
- The change allows users to configure env variables as part of tanzu config file - Env configuration can be set using `tanzu config set env.<variable> <value>` command - User can unset the already configured settings with new `tanzu config unset env.<variable>` command - In the case of conflicts, meaning user has set environment variable outside tanzu config file with `export FOO=bar` and as part of tanzu config file, higer precedence is given to user configured environment variable to allow user to override config for one off command runs
1 parent 4ed8343 commit 701f6e7

File tree

6 files changed

+194
-18
lines changed

6 files changed

+194
-18
lines changed

apis/config/v1alpha1/clientconfig.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ func (c *ClientConfig) IsConfigFeatureActivated(featurePath string) (bool, error
8686
return booleanValue, nil
8787
}
8888

89+
// GetEnvConfigurations returns a map of environment variables to values
90+
// it returns nil if configuration is not yet defined
91+
func (c *ClientConfig) GetEnvConfigurations() map[string]string {
92+
if c.ClientOptions == nil || c.ClientOptions.Env == nil {
93+
return nil
94+
}
95+
return c.ClientOptions.Env
96+
}
97+
8998
// SplitFeaturePath splits a features path into the pluginName and the featureName
9099
// For example "features.management-cluster.dual-stack" returns "management-cluster", "dual-stack"
91100
// An error results from a malformed path, including any path that does not start with "features."

apis/config/v1alpha1/clientconfig_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type ClientOptions struct {
9393
// CLI options specific to the CLI.
9494
CLI *CLIOptions `json:"cli,omitempty" yaml:"cli"`
9595
Features map[string]FeatureMap `json:"features,omitempty" yaml:"features"`
96+
Env map[string]string `json:"env,omitempty" yaml:"env"`
9697
}
9798

9899
// FeatureMap is simply a hash table, but needs an explicit type to be an object in another hash map (cf ClientOptions.Features)

pkg/v1/cli/command/core/config.go

Lines changed: 119 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,19 @@ import (
2020
"github.com/vmware-tanzu/tanzu-framework/pkg/v1/config"
2121
)
2222

23+
// ConfigLiterals used with set/unset commands
24+
const (
25+
ConfigLiteralFeatures = "features"
26+
ConfigLiteralEnv = "env"
27+
)
28+
2329
func init() {
2430
configCmd.SetUsageFunc(cli.SubCmdUsageFunc)
2531
configCmd.AddCommand(
2632
getConfigCmd,
2733
initConfigCmd,
2834
setConfigCmd,
35+
unsetConfigCmd,
2936
serversCmd,
3037
)
3138
serversCmd.AddCommand(listServersCmd)
@@ -67,7 +74,7 @@ var getConfigCmd = &cobra.Command{
6774

6875
var setConfigCmd = &cobra.Command{
6976
Use: "set <path> <value>",
70-
Short: "Set config values at the given path. path values: [unstable-versions, features.global.<feature>, features.<plugin>.<feature>]",
77+
Short: "Set config values at the given path. path values: [unstable-versions, features.global.<feature>, features.<plugin>.<feature>, env.global.<variable>, env.<plugin>.<variable>]",
7178
RunE: func(cmd *cobra.Command, args []string) error {
7279
if len(args) < 2 {
7380
return errors.Errorf("both path and value are required")
@@ -80,7 +87,7 @@ var setConfigCmd = &cobra.Command{
8087
return err
8188
}
8289

83-
err = setFeature(cfg, args[0], args[1])
90+
err = setConfiguration(cfg, args[0], args[1])
8491
if err != nil {
8592
return err
8693
}
@@ -89,8 +96,8 @@ var setConfigCmd = &cobra.Command{
8996
},
9097
}
9198

92-
// setFeature sets the key-value pair for the given path
93-
func setFeature(cfg *configv1alpha1.ClientConfig, pathParam, value string) error {
99+
// setConfiguration sets the key-value pair for the given path
100+
func setConfiguration(cfg *configv1alpha1.ClientConfig, pathParam, value string) error {
94101
// special cases:
95102
// backward compatibility
96103
if pathParam == "unstable-versions" {
@@ -99,17 +106,28 @@ func setFeature(cfg *configv1alpha1.ClientConfig, pathParam, value string) error
99106

100107
// parse the param
101108
paramArray := strings.Split(pathParam, ".")
102-
if len(paramArray) != 3 {
103-
return errors.New("unable to parse config path parameter into three parts [" + pathParam + "] (was expecting features.<plugin>.<feature>)")
109+
if len(paramArray) < 2 {
110+
return errors.New("unable to parse config path parameter into parts [" + pathParam + "] (was expecting 'features.<plugin>.<feature>' or 'env.<env_variable>')")
104111
}
105112

106-
featuresLiteral := paramArray[0]
107-
plugin := paramArray[1]
108-
key := paramArray[2]
113+
configLiteral := paramArray[0]
114+
115+
switch configLiteral {
116+
case ConfigLiteralFeatures:
117+
return setFeatures(cfg, paramArray, value)
118+
case ConfigLiteralEnv:
119+
return setEnvs(cfg, paramArray, value)
120+
default:
121+
return errors.New("unsupported config path parameter [" + configLiteral + "] (was expecting 'features.<plugin>.<feature>' or 'env.<env_variable>')")
122+
}
123+
}
109124

110-
if featuresLiteral != "features" {
111-
return errors.New("unsupported config path parameter [" + featuresLiteral + "] (was expecting 'features.<plugin>.<feature>')")
125+
func setFeatures(cfg *configv1alpha1.ClientConfig, paramArray []string, value string) error {
126+
if len(paramArray) != 3 {
127+
return errors.New("unable to parse config path parameter into three parts [" + strings.Join(paramArray, ".") + "] (was expecting 'features.<plugin>.<feature>'")
112128
}
129+
plugin := paramArray[1]
130+
featureName := paramArray[2]
113131

114132
if cfg.ClientOptions == nil {
115133
cfg.ClientOptions = &configv1alpha1.ClientOptions{}
@@ -120,8 +138,24 @@ func setFeature(cfg *configv1alpha1.ClientConfig, pathParam, value string) error
120138
if cfg.ClientOptions.Features[plugin] == nil {
121139
cfg.ClientOptions.Features[plugin] = configv1alpha1.FeatureMap{}
122140
}
123-
cfg.ClientOptions.Features[plugin][key] = value
141+
cfg.ClientOptions.Features[plugin][featureName] = value
142+
return nil
143+
}
144+
145+
func setEnvs(cfg *configv1alpha1.ClientConfig, paramArray []string, value string) error {
146+
if len(paramArray) != 2 {
147+
return errors.New("unable to parse config path parameter into two parts [" + strings.Join(paramArray, ".") + "] (was expecting 'env.<variable>'")
148+
}
149+
envVariable := paramArray[1]
124150

151+
if cfg.ClientOptions == nil {
152+
cfg.ClientOptions = &configv1alpha1.ClientOptions{}
153+
}
154+
if cfg.ClientOptions.Env == nil {
155+
cfg.ClientOptions.Env = make(map[string]string)
156+
}
157+
158+
cfg.ClientOptions.Env[envVariable] = value
125159
return nil
126160
}
127161

@@ -256,3 +290,76 @@ var deleteServersCmd = &cobra.Command{
256290
return nil
257291
},
258292
}
293+
294+
var unsetConfigCmd = &cobra.Command{
295+
Use: "unset <path>",
296+
Short: "Unset config values at the given path. path values: [features.global.<feature>, features.<plugin>.<feature>, env.global.<variable>, env.<plugin>.<variable>]",
297+
RunE: func(cmd *cobra.Command, args []string) error {
298+
if len(args) < 1 {
299+
return errors.Errorf("path is required")
300+
}
301+
if len(args) > 1 {
302+
return errors.Errorf("only path is allowed")
303+
}
304+
cfg, err := config.GetClientConfig()
305+
if err != nil {
306+
return err
307+
}
308+
309+
err = unsetConfiguration(cfg, args[0])
310+
if err != nil {
311+
return err
312+
}
313+
314+
return config.StoreClientConfig(cfg)
315+
},
316+
}
317+
318+
// unsetConfiguration unsets the key-value pair for the given path and removes it
319+
func unsetConfiguration(cfg *configv1alpha1.ClientConfig, pathParam string) error {
320+
// parse the param
321+
paramArray := strings.Split(pathParam, ".")
322+
if len(paramArray) < 2 {
323+
return errors.New("unable to parse config path parameter into parts [" + pathParam + "] (was expecting 'features.<plugin>.<feature>' or 'env.<env_variable>')")
324+
}
325+
326+
configLiteral := paramArray[0]
327+
328+
switch configLiteral {
329+
case ConfigLiteralFeatures:
330+
return unsetFeatures(cfg, paramArray)
331+
case ConfigLiteralEnv:
332+
return unsetEnvs(cfg, paramArray)
333+
default:
334+
return errors.New("unsupported config path parameter [" + configLiteral + "] (was expecting 'features.<plugin>.<feature>' or 'env.<env_variable>')")
335+
}
336+
}
337+
338+
func unsetFeatures(cfg *configv1alpha1.ClientConfig, paramArray []string) error {
339+
if len(paramArray) != 3 {
340+
return errors.New("unable to parse config path parameter into three parts [" + strings.Join(paramArray, ".") + "] (was expecting 'features.<plugin>.<feature>'")
341+
}
342+
plugin := paramArray[1]
343+
featureName := paramArray[2]
344+
345+
if cfg.ClientOptions == nil || cfg.ClientOptions.Features == nil ||
346+
cfg.ClientOptions.Features[plugin] == nil {
347+
return nil
348+
}
349+
delete(cfg.ClientOptions.Features[plugin], featureName)
350+
return nil
351+
}
352+
353+
func unsetEnvs(cfg *configv1alpha1.ClientConfig, paramArray []string) error {
354+
if len(paramArray) != 2 {
355+
return errors.New("unable to parse config path parameter into two parts [" + strings.Join(paramArray, ".") + "] (was expecting 'env.<env_variable>'")
356+
}
357+
358+
envVariable := paramArray[1]
359+
if cfg.ClientOptions == nil || cfg.ClientOptions.Env == nil {
360+
return nil
361+
}
362+
delete(cfg.ClientOptions.Env, envVariable)
363+
364+
return nil
365+
}

pkg/v1/cli/command/core/config_test.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import (
77
"strings"
88
"testing"
99

10+
"github.com/stretchr/testify/assert"
11+
1012
configv1alpha1 "github.com/vmware-tanzu/tanzu-framework/apis/config/v1alpha1"
1113
)
1214

1315
// Test_config_MalformedPathArg validates functionality when an invalid argument is provided.
1416
func Test_config_MalformedPathArg(t *testing.T) {
15-
err := setFeature(nil, "invalid-arg", "")
17+
err := setConfiguration(nil, "invalid-arg", "")
1618
if err == nil {
1719
t.Error("Malformed path argument should have resulted in an error")
1820
}
@@ -24,7 +26,7 @@ func Test_config_MalformedPathArg(t *testing.T) {
2426

2527
// Test_config_InvalidPathArg validates functionality when an invalid argument is provided.
2628
func Test_config_InvalidPathArg(t *testing.T) {
27-
err := setFeature(nil, "shouldbefeatures.plugin.feature", "")
29+
err := setConfiguration(nil, "shouldbefeatures.plugin.feature", "")
2830
if err == nil {
2931
t.Error("Invalid path argument should have resulted in an error")
3032
}
@@ -37,7 +39,7 @@ func Test_config_InvalidPathArg(t *testing.T) {
3739
// Test_config_UnstableVersions validates functionality when path argument unstable-versions is provided.
3840
func Test_config_UnstableVersions(t *testing.T) {
3941
cfg := &configv1alpha1.ClientConfig{}
40-
err := setFeature(cfg, "unstable-versions", "experimental")
42+
err := setConfiguration(cfg, "unstable-versions", "experimental")
4143
if err != nil {
4244
t.Errorf("Unexpected error returned for unstable-versions path argument: %s", err.Error())
4345
}
@@ -50,7 +52,7 @@ func Test_config_UnstableVersions(t *testing.T) {
5052
// Test_config_InvalidUnstableVersions validates functionality when invalid unstable-versions is provided.
5153
func Test_config_InvalidUnstableVersions(t *testing.T) {
5254
cfg := &configv1alpha1.ClientConfig{}
53-
err := setFeature(cfg, "unstable-versions", "invalid-unstable-versions-value")
55+
err := setConfiguration(cfg, "unstable-versions", "invalid-unstable-versions-value")
5456
if err == nil {
5557
t.Error("Invalid unstable-versions should have resulted in error")
5658
}
@@ -64,7 +66,7 @@ func Test_config_InvalidUnstableVersions(t *testing.T) {
6466
func Test_config_GlobalFeature(t *testing.T) {
6567
cfg := &configv1alpha1.ClientConfig{}
6668
value := "bar"
67-
err := setFeature(cfg, "features.global.foo", value)
69+
err := setConfiguration(cfg, "features.global.foo", value)
6870
if err != nil {
6971
t.Errorf("Unexpected error returned for global features path argument: %s", err.Error())
7072
}
@@ -78,7 +80,7 @@ func Test_config_GlobalFeature(t *testing.T) {
7880
func Test_config_Feature(t *testing.T) {
7981
cfg := &configv1alpha1.ClientConfig{}
8082
value := "barr"
81-
err := setFeature(cfg, "features.any-plugin.foo", value)
83+
err := setConfiguration(cfg, "features.any-plugin.foo", value)
8284
if err != nil {
8385
t.Errorf("Unexpected error returned for any-plugin features path argument: %s", err.Error())
8486
}
@@ -87,3 +89,29 @@ func Test_config_Feature(t *testing.T) {
8789
t.Error("cfg.ClientOptions.Features[\"any-plugin\"][\"foo\"] was not assigned the value \"" + value + "\"")
8890
}
8991
}
92+
93+
// Test_config_SetUnsetEnv validates set and unset functionality when env config path argument is provided.
94+
func Test_config_SetUnsetEnv(t *testing.T) {
95+
assert := assert.New(t)
96+
97+
cfg := &configv1alpha1.ClientConfig{}
98+
value := "baar"
99+
err := setConfiguration(cfg, "env.foo", value)
100+
assert.Nil(err)
101+
assert.Equal(value, cfg.ClientOptions.Env["foo"])
102+
103+
err = unsetConfiguration(cfg, "env.foo")
104+
assert.Nil(err)
105+
assert.Equal(cfg.ClientOptions.Env["foo"], "")
106+
}
107+
108+
// Test_config_IncorrectConfigLiteral validates incorrect config literal
109+
func Test_config_IncorrectConfigLiteral(t *testing.T) {
110+
assert := assert.New(t)
111+
112+
cfg := &configv1alpha1.ClientConfig{}
113+
value := "b"
114+
err := setConfiguration(cfg, "fake.any-plugin.foo", value)
115+
assert.NotNil(err)
116+
assert.Contains(err.Error(), "unsupported config path parameter [fake] (was expecting 'features.<plugin>.<feature>' or 'env.<env_variable>')")
117+
}

pkg/v1/cli/command/core/root.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ func NewRootCmd() (*cobra.Command, error) {
7878
}
7979
}
8080

81+
// configure defined global environment variables
82+
// under tanzu config file
83+
config.ConfigureEnvVariables()
84+
8185
for _, plugin := range plugins {
8286
RootCmd.AddCommand(cli.GetCmd(plugin))
8387
}

pkg/v1/config/clientconfig.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,30 @@ func IsFeatureActivated(feature string) bool {
507507
}
508508
return status
509509
}
510+
511+
// GetEnvConfigurations returns a map of configured environment variables
512+
// to values as part of tanzu configuration file
513+
// it returns nil if configuration is not yet defined
514+
func GetEnvConfigurations() map[string]string {
515+
cfg, err := GetClientConfig()
516+
if err != nil {
517+
return nil
518+
}
519+
return cfg.GetEnvConfigurations()
520+
}
521+
522+
// ConfigureEnvVariables reads and configures provided environment variables
523+
// as part of tanzu configuration file
524+
func ConfigureEnvVariables() {
525+
envMap := GetEnvConfigurations()
526+
if envMap == nil {
527+
return
528+
}
529+
for variable, value := range envMap {
530+
// If environment variable is not already set
531+
// set the environment variable
532+
if os.Getenv(variable) == "" {
533+
os.Setenv(variable, value)
534+
}
535+
}
536+
}

0 commit comments

Comments
 (0)