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

Commit 5a1870b

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.global.<variable> <value>` or `tanzu config set env.<plugin>.<variable>` command - User can unset the already configured settings with new `tanzu config unset env.<plugin>.<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 5a1870b

File tree

7 files changed

+200
-16
lines changed

7 files changed

+200
-16
lines changed

apis/config/v1alpha1/clientconfig.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ 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(plugin string) EnvMap {
92+
if c.ClientOptions == nil || c.ClientOptions.Env == nil ||
93+
c.ClientOptions.Env[plugin] == nil {
94+
return nil
95+
}
96+
return c.ClientOptions.Env[plugin]
97+
}
98+
8999
// SplitFeaturePath splits a features path into the pluginName and the featureName
90100
// For example "features.management-cluster.dual-stack" returns "management-cluster", "dual-stack"
91101
// An error results from a malformed path, including any path that does not start with "features."

apis/config/v1alpha1/clientconfig_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,15 @@ 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]EnvMap `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)
99100
type FeatureMap map[string]string
100101

102+
// EnvMap is simply a hash table, but needs an explicit type to be an object in another hash map (cf ClientOptions.Env)
103+
type EnvMap map[string]string
104+
101105
// CLIOptions are options for the CLI.
102106
type CLIOptions struct {
103107
// Repositories are the plugin repositories.

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

Lines changed: 101 additions & 10 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" {
@@ -100,17 +107,26 @@ func setFeature(cfg *configv1alpha1.ClientConfig, pathParam, value string) error
100107
// parse the param
101108
paramArray := strings.Split(pathParam, ".")
102109
if len(paramArray) != 3 {
103-
return errors.New("unable to parse config path parameter into three parts [" + pathParam + "] (was expecting features.<plugin>.<feature>)")
110+
return errors.New("unable to parse config path parameter into three parts [" + pathParam + "] (was expecting 'features.<plugin>.<feature>' or 'env.<plugin>.<env_variable>')")
104111
}
105112

106-
featuresLiteral := paramArray[0]
113+
configLiteral := paramArray[0]
107114
plugin := paramArray[1]
108115
key := paramArray[2]
109116

110-
if featuresLiteral != "features" {
111-
return errors.New("unsupported config path parameter [" + featuresLiteral + "] (was expecting 'features.<plugin>.<feature>')")
117+
switch configLiteral {
118+
case ConfigLiteralFeatures:
119+
setFeatures(cfg, plugin, key, value)
120+
case ConfigLiteralEnv:
121+
setEnvs(cfg, plugin, key, value)
122+
default:
123+
return errors.New("unsupported config path parameter [" + configLiteral + "] (was expecting 'features.<plugin>.<feature>' or 'env.<plugin>.<env_variable>')")
112124
}
113125

126+
return nil
127+
}
128+
129+
func setFeatures(cfg *configv1alpha1.ClientConfig, plugin, featureName, value string) {
114130
if cfg.ClientOptions == nil {
115131
cfg.ClientOptions = &configv1alpha1.ClientOptions{}
116132
}
@@ -120,9 +136,20 @@ func setFeature(cfg *configv1alpha1.ClientConfig, pathParam, value string) error
120136
if cfg.ClientOptions.Features[plugin] == nil {
121137
cfg.ClientOptions.Features[plugin] = configv1alpha1.FeatureMap{}
122138
}
123-
cfg.ClientOptions.Features[plugin][key] = value
139+
cfg.ClientOptions.Features[plugin][featureName] = value
140+
}
124141

125-
return nil
142+
func setEnvs(cfg *configv1alpha1.ClientConfig, plugin, envVariable, value string) {
143+
if cfg.ClientOptions == nil {
144+
cfg.ClientOptions = &configv1alpha1.ClientOptions{}
145+
}
146+
if cfg.ClientOptions.Env == nil {
147+
cfg.ClientOptions.Env = make(map[string]configv1alpha1.EnvMap)
148+
}
149+
if cfg.ClientOptions.Env[plugin] == nil {
150+
cfg.ClientOptions.Env[plugin] = configv1alpha1.EnvMap{}
151+
}
152+
cfg.ClientOptions.Env[plugin][envVariable] = value
126153
}
127154

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

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

Lines changed: 47 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,42 @@ 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_GlobalEnv validates functionality when env feature path argument is provided.
94+
func Test_config_GlobalEnv(t *testing.T) {
95+
cfg := &configv1alpha1.ClientConfig{}
96+
value := "baar"
97+
err := setConfiguration(cfg, "env.global.foo", value)
98+
if err != nil {
99+
t.Errorf("Unexpected error returned for global env path argument: %s", err.Error())
100+
}
101+
102+
if cfg.ClientOptions.Env["global"]["foo"] != value {
103+
t.Error("cfg.ClientOptions.Env[\"global\"][\"foo\"] was not assigned the value \"" + value + "\"")
104+
}
105+
}
106+
107+
// Test_config_Env validates functionality when normal env path argument is provided.
108+
func Test_config_Env(t *testing.T) {
109+
cfg := &configv1alpha1.ClientConfig{}
110+
value := "baarr"
111+
err := setConfiguration(cfg, "env.any-plugin.foo", value)
112+
if err != nil {
113+
t.Errorf("Unexpected error returned for any-plugin env path argument: %s", err.Error())
114+
}
115+
116+
if cfg.ClientOptions.Env["any-plugin"]["foo"] != value {
117+
t.Error("cfg.ClientOptions.Features[\"any-plugin\"][\"foo\"] was not assigned the value \"" + value + "\"")
118+
}
119+
}
120+
121+
// Test_config_Env validates functionality when normal env path argument is provided.
122+
func Test_config_IncorrectConfigLiteral(t *testing.T) {
123+
assert := assert.New(t)
124+
125+
cfg := &configv1alpha1.ClientConfig{}
126+
value := "b"
127+
err := setConfiguration(cfg, "fake.any-plugin.foo", value)
128+
assert.NotNil(err)
129+
assert.Contains(err.Error(), "unsupported config path parameter [fake] (was expecting 'features.<plugin>.<feature>' or 'env.<plugin>.<env_variable>')")
130+
}

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

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

81+
// configure defined global environment variables
82+
// under tanzu config file
83+
// global environment variables can be defined as `env.global.FOO`
84+
config.ConfigureEnvVariables("global")
85+
8186
for _, plugin := range plugins {
8287
RootCmd.AddCommand(cli.GetCmd(plugin))
8388
}

pkg/v1/cli/runner.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"path/filepath"
1414

1515
"github.com/aunum/log"
16+
17+
"github.com/vmware-tanzu/tanzu-framework/pkg/v1/config"
1618
)
1719

1820
// Runner is a plugin runner.
@@ -38,6 +40,9 @@ func NewRunner(name, pluginAbsPath string, args []string, options ...Option) *Ru
3840

3941
// Run runs a plugin.
4042
func (r *Runner) Run(ctx context.Context) error {
43+
// configures plugin specific environment variables
44+
// defined under tanzu config file
45+
config.ConfigureEnvVariables(r.name)
4146
return r.runStdOutput(ctx, r.pluginPath())
4247
}
4348

pkg/v1/config/clientconfig.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,3 +507,31 @@ 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(plugin string) configv1alpha1.EnvMap {
515+
cfg, err := GetClientConfig()
516+
if err != nil {
517+
return nil
518+
}
519+
return cfg.GetEnvConfigurations(plugin)
520+
}
521+
522+
// ConfigureEnvVariables reads and configures provided environment variables
523+
// as part of tanzu configuration file based on the provided plugin name
524+
// plugin can be a name of the plugin or 'global' if it generic variable
525+
func ConfigureEnvVariables(plugin string) {
526+
envMap := GetEnvConfigurations(plugin)
527+
if envMap == nil {
528+
return
529+
}
530+
for variable, value := range envMap {
531+
// If environment variable is not already set
532+
// set the environment variable
533+
if os.Getenv(variable) == "" {
534+
os.Setenv(variable, value)
535+
}
536+
}
537+
}

0 commit comments

Comments
 (0)