Repository: klothoplatform/klotho (go)

Issue:
allow custom environment variables for any execution unit
today we only pass back environment variables for lambda. Assuming that we can do this uniform for all exec unit types we should add this to generic config (not pulumi params) and then write these env vars as core.EnvironmentVariables so its reusable across platforms and IaCs

Pull request description:
allow env vars for all exec units from config
• Does any part of it require special attention?
• Does it relate to or fix any issue? closes #119 

allows env vars to be attached to an execution unit through config in a standard way across types.

Changes to pro are in a different pr

### Standard checks

- **Unit tests**: Any special considerations? Added unit tests for exec unit plugin
- **Docs**: Do we need to update any docs, internal or public? need to add docs
- **Backwards compatibility**: Will this break existing apps? If so, what would be the extra work required to keep them working? yes, new feature and wont conflict with pulumi passthrough


Patch:
diff --git a/pkg/config/config.go b/pkg/config/config.go
index c3dbdbd2..10285ed3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -41,10 +41,11 @@ type (
 	}
 
 	ExecutionUnit struct {
-		Type             string            `json:"type" yaml:"type" toml:"type"`
-		NetworkPlacement string            `json:"network_placement,omitempty" yaml:"network_placement,omitempty" toml:"network_placement,omitempty"`
-		HelmChartOptions *HelmChartOptions `json:"helm_chart_options,omitempty" yaml:"helm_chart_options,omitempty" toml:"helm_chart_options,omitempty"`
-		InfraParams      InfraParams       `json:"pulumi_params,omitempty" yaml:"pulumi_params,omitempty" toml:"pulumi_params,omitempty"`
+		Type                 string            `json:"type" yaml:"type" toml:"type"`
+		NetworkPlacement     string            `json:"network_placement,omitempty" yaml:"network_placement,omitempty" toml:"network_placement,omitempty"`
+		EnvironmentVariables map[string]string `json:"environment_variables,omitempty" yaml:"environment_variables,omitempty" toml:"environment_variables,omitempty"`
+		HelmChartOptions     *HelmChartOptions `json:"helm_chart_options,omitempty" yaml:"helm_chart_options,omitempty" toml:"helm_chart_options,omitempty"`
+		InfraParams          InfraParams       `json:"pulumi_params,omitempty" yaml:"pulumi_params,omitempty" toml:"pulumi_params,omitempty"`
 	}
 
 	// A HelmChartOptions represents configuration for execution units attempting to generate helm charts
@@ -153,6 +154,10 @@ func (cfg *ExecutionUnit) Merge(other ExecutionUnit) {
 	if other.NetworkPlacement == "" {
 		cfg.NetworkPlacement = "private"
 	}
+	cfg.EnvironmentVariables = other.EnvironmentVariables
+	if cfg.EnvironmentVariables == nil {
+		cfg.EnvironmentVariables = make(map[string]string)
+	}
 	cfg.HelmChartOptions = other.HelmChartOptions
 	cfg.InfraParams.Merge(other.InfraParams)
 }
diff --git a/pkg/exec_unit/plugin_exec_unit.go b/pkg/exec_unit/plugin_exec_unit.go
index 8fa3bb89..08bab824 100644
--- a/pkg/exec_unit/plugin_exec_unit.go
+++ b/pkg/exec_unit/plugin_exec_unit.go
@@ -23,7 +23,15 @@ func (p ExecUnitPlugin) Transform(result *core.CompilationResult, deps *core.Dep
 		Name:       "main",
 		Executable: core.NewExecutable(),
 	}
-	unit.ExecType = p.Config.GetExecutionUnit(unit.Name).Type
+	cfg := p.Config.GetExecutionUnit(unit.Name)
+	unit.ExecType = cfg.Type
+
+	for key, value := range cfg.EnvironmentVariables {
+		unit.EnvironmentVariables = append(unit.EnvironmentVariables, core.EnvironmentVariable{
+			Name:  key,
+			Value: value,
+		})
+	}
 
 	for _, f := range inputR.(*core.InputFiles).Files() {
 		if _, ok := f.(*core.SourceFile); ok {
diff --git a/pkg/infra/pulumi_aws/deploylib.ts b/pkg/infra/pulumi_aws/deploylib.ts
index dbc12524..bfc06ad5 100755
--- a/pkg/infra/pulumi_aws/deploylib.ts
+++ b/pkg/infra/pulumi_aws/deploylib.ts
@@ -574,8 +574,12 @@ export class CloudCCLib {
 
         if (env_vars) {
             for (const v of env_vars) {
-                const result = this.getEnvVarForDependency(v)
-                additionalEnvVars[result[0]] = result[1]
+                if (v.Kind == '') {
+                    additionalEnvVars[v.Name] = v.Value
+                } else {
+                    const result = this.getEnvVarForDependency(v)
+                    additionalEnvVars[result[0]] = result[1]
+                }
             }
         }
 


Test patch:
diff --git a/pkg/exec_unit/plugin_exec_unit_test.go b/pkg/exec_unit/plugin_exec_unit_test.go
new file mode 100644
index 00000000..511ba82a
--- /dev/null
+++ b/pkg/exec_unit/plugin_exec_unit_test.go
@@ -0,0 +1,85 @@
+package execunit
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/klothoplatform/klotho/pkg/config"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_environmentVarsAddedToUnit(t *testing.T) {
+	tests := []struct {
+		name         string
+		envVars      map[string]string
+		want         []core.EnvironmentVariable
+		wantExecUnit bool
+	}{
+		{
+			name: "no exec unit",
+			envVars: map[string]string{
+				"key1": "value1",
+				"key2": "value2",
+			},
+			wantExecUnit: false,
+		},
+		{
+			name: "add env vars",
+			envVars: map[string]string{
+				"key1": "value1",
+				"key2": "value2",
+			},
+			want: []core.EnvironmentVariable{
+				{
+					Name:  "key1",
+					Value: "value1",
+				},
+				{
+					Name:  "key2",
+					Value: "value2",
+				},
+			},
+			wantExecUnit: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+
+			cfg := config.Application{
+				ExecutionUnits: map[string]*config.ExecutionUnit{
+					"main": {EnvironmentVariables: tt.envVars},
+				},
+			}
+			p := ExecUnitPlugin{Config: &cfg}
+			result := &core.CompilationResult{}
+
+			inputFiles := &core.InputFiles{}
+			if tt.wantExecUnit {
+				f, err := core.NewSourceFile("test", strings.NewReader("test"), testAnnotationLang)
+				if assert.Nil(err) {
+					inputFiles.Add(f)
+				}
+			} else {
+				inputFiles.Add(&core.FileRef{
+					FPath: "test",
+				})
+			}
+
+			result.Add(inputFiles)
+			err := p.Transform(result, &core.Dependencies{})
+			if !assert.NoError(err) {
+				return
+			}
+			units := core.GetResourcesOfType[*core.ExecutionUnit](result)
+			if tt.wantExecUnit {
+				assert.Len(units, 1)
+				assert.ElementsMatch(tt.want, units[0].EnvironmentVariables)
+			} else {
+				assert.Len(units, 0)
+			}
+
+		})
+	}
+}