Skip to content

Commit 6185537

Browse files
authored
feat: add env variable support (#209)
* feat: add env variable support
1 parent 037132f commit 6185537

File tree

8 files changed

+166
-59
lines changed

8 files changed

+166
-59
lines changed

action.yml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,6 @@ outputs:
6565
runs:
6666
using: composite
6767
steps:
68-
- name: Validate General Input Configuration
69-
run: |
70-
if [[ "${{ inputs.setup-aws }}" == "false" && "${{ inputs.setup-google-cloud }}" == "false" ]]; then
71-
echo "Either 'setup-aws' or 'setup-google-cloud' input must be set to 'true'"
72-
else
73-
exit 0
74-
fi
75-
exit 1
76-
shell: bash
77-
7868
- name: Validate Input Configuration for Google
7969
run: |
8070
if [[ -z ${{ toJSON(inputs.google-auth-credentials) }} && -z "${{ inputs.google-workload-identity-provider }}" ]]; then

pkg/configuration/digger_config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ type DiggerConfigYaml struct {
2525
GenerateProjectsConfig *GenerateProjectsConfig `yaml:"generate_projects"`
2626
}
2727

28+
type EnvVarConfig struct {
29+
Name string `yaml:"name"`
30+
ValueFrom string `yaml:"value_from"`
31+
Value string `yaml:"value"`
32+
}
33+
2834
type DiggerConfig struct {
2935
Projects []Project
3036
AutoMerge bool
@@ -50,11 +56,17 @@ type Stage struct {
5056
}
5157

5258
type Workflow struct {
59+
EnvVars EnvVars `yaml:"env_vars"`
5360
Plan *Stage `yaml:"plan,omitempty"`
5461
Apply *Stage `yaml:"apply,omitempty"`
5562
Configuration *WorkflowConfiguration `yaml:"workflow_configuration"`
5663
}
5764

65+
type EnvVars struct {
66+
State []EnvVarConfig `yaml:"state"`
67+
Commands []EnvVarConfig `yaml:"commands"`
68+
}
69+
5870
type DirWalker interface {
5971
GetDirs(workingDir string) ([]string, error)
6072
}
@@ -125,6 +137,10 @@ func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
125137
},
126138
},
127139
},
140+
EnvVars: EnvVars{
141+
State: []EnvVarConfig{},
142+
Commands: []EnvVarConfig{},
143+
},
128144
}
129145
if err := unmarshal(&raw); err != nil {
130146
return err

pkg/configuration/digger_config_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,56 @@ workflows:
116116
assert.Equal(t, Step{Action: "run", Value: "echo \"hello\""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct")
117117
}
118118

119+
func TestEnvVarsConfiguration(t *testing.T) {
120+
tempDir, teardown := setUp()
121+
defer teardown()
122+
123+
diggerCfg := `
124+
projects:
125+
- name: dev
126+
branch: /main/
127+
dir: .
128+
workspace: default
129+
terragrunt: false
130+
workflow: myworkflow
131+
workflows:
132+
myworkflow:
133+
plan:
134+
steps:
135+
- init:
136+
extra_args: ["-lock=false"]
137+
- plan:
138+
extra_args: ["-lock=false"]
139+
- run: echo "hello"
140+
apply:
141+
steps:
142+
- apply:
143+
extra_args: ["-lock=false"]
144+
workflow_configuration:
145+
on_pull_request_pushed: [digger plan]
146+
on_pull_request_closed: [digger unlock]
147+
on_commit_to_default: [digger apply]
148+
env_vars:
149+
state:
150+
- name: TF_VAR_state
151+
value: s3://mybucket/terraform.tfstate
152+
commands:
153+
- name: TF_VAR_command
154+
value: plan
155+
`
156+
deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg)
157+
defer deleteFile()
158+
159+
dg, err := NewDiggerConfig(tempDir, &FileSystemDirWalker{})
160+
assert.NoError(t, err, "expected error to be nil")
161+
assert.Equal(t, []EnvVarConfig{
162+
{Name: "TF_VAR_state", Value: "s3://mybucket/terraform.tfstate"},
163+
}, dg.Workflows["myworkflow"].EnvVars.State, "parsed struct does not match expected struct")
164+
assert.Equal(t, []EnvVarConfig{
165+
{Name: "TF_VAR_command", Value: "plan"},
166+
}, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct")
167+
}
168+
119169
func TestDefaultValuesForWorkflowConfiguration(t *testing.T) {
120170
tempDir, teardown := setUp()
121171
defer teardown()

pkg/digger/digger.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ func RunCommandsPerProject(commandsPerProject []ProjectCommand, repoOwner string
8181
}
8282

8383
commandRunner := CommandRunner{}
84-
8584
diggerExecutor := DiggerExecutor{
8685
projectCommands.ProjectName,
86+
projectCommands.StateEnvVars,
87+
projectCommands.CommandEnvVars,
8788
projectCommands.ApplyStage,
8889
projectCommands.PlanStage,
8990
commandRunner,
@@ -186,6 +187,8 @@ type ProjectCommand struct {
186187
Commands []string
187188
ApplyStage *configuration.Stage
188189
PlanStage *configuration.Stage
190+
StateEnvVars map[string]string
191+
CommandEnvVars map[string]string
189192
}
190193

191194
func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.Project, workflows map[string]configuration.Workflow) ([]ProjectCommand, error) {
@@ -199,6 +202,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
199202
if !ok {
200203
workflow = *defaultWorkflow()
201204
}
205+
206+
stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)
207+
202208
if event.Action == "closed" && event.PullRequest.Merged && event.PullRequest.Base.Ref == event.Repository.DefaultBranch {
203209
commandsPerProject = append(commandsPerProject, ProjectCommand{
204210
ProjectName: project.Name,
@@ -208,6 +214,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
208214
Commands: workflow.Configuration.OnCommitToDefault,
209215
ApplyStage: workflow.Apply,
210216
PlanStage: workflow.Plan,
217+
CommandEnvVars: commandEnvVars,
218+
StateEnvVars: stateEnvVars,
211219
})
212220
} else if event.Action == "opened" || event.Action == "reopened" || event.Action == "synchronize" {
213221
commandsPerProject = append(commandsPerProject, ProjectCommand{
@@ -218,6 +226,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
218226
Commands: workflow.Configuration.OnPullRequestPushed,
219227
ApplyStage: workflow.Apply,
220228
PlanStage: workflow.Plan,
229+
CommandEnvVars: commandEnvVars,
230+
StateEnvVars: stateEnvVars,
221231
})
222232
} else if event.Action == "closed" {
223233
commandsPerProject = append(commandsPerProject, ProjectCommand{
@@ -228,6 +238,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
228238
Commands: workflow.Configuration.OnPullRequestClosed,
229239
ApplyStage: workflow.Apply,
230240
PlanStage: workflow.Plan,
241+
CommandEnvVars: commandEnvVars,
242+
StateEnvVars: stateEnvVars,
231243
})
232244
}
233245
}
@@ -243,6 +255,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
243255
if !ok {
244256
workflow = *defaultWorkflow()
245257
}
258+
259+
stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)
260+
246261
workspace := project.Workspace
247262
workspaceOverride, err := parseWorkspace(event.Comment.Body)
248263
if err != nil {
@@ -259,6 +274,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
259274
Commands: []string{command},
260275
ApplyStage: workflow.Apply,
261276
PlanStage: workflow.Plan,
277+
CommandEnvVars: commandEnvVars,
278+
StateEnvVars: stateEnvVars,
262279
})
263280
}
264281
}
@@ -269,6 +286,29 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
269286
}
270287
}
271288

289+
func collectEnvVars(envs configuration.EnvVars) (map[string]string, map[string]string) {
290+
stateEnvVars := map[string]string{}
291+
292+
for _, envvar := range envs.State {
293+
if envvar.Value != "" {
294+
stateEnvVars[envvar.Name] = envvar.Value
295+
} else if envvar.ValueFrom != "" {
296+
stateEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
297+
}
298+
}
299+
300+
commandEnvVars := map[string]string{}
301+
302+
for _, envvar := range envs.Commands {
303+
if envvar.Value != "" {
304+
commandEnvVars[envvar.Name] = envvar.Value
305+
} else if envvar.ValueFrom != "" {
306+
commandEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
307+
}
308+
}
309+
return stateEnvVars, commandEnvVars
310+
}
311+
272312
func parseWorkspace(comment string) (string, error) {
273313
re := regexp.MustCompile(`-w(?:\s+(\S+)|$)`)
274314
matches := re.FindAllStringSubmatch(comment, -1)
@@ -299,6 +339,8 @@ func parseProjectName(comment string) string {
299339

300340
type DiggerExecutor struct {
301341
projectName string
342+
stateEnvVars map[string]string
343+
commandEnvVars map[string]string
302344
applyStage *configuration.Stage
303345
planStage *configuration.Stage
304346
commandRunner CommandRun
@@ -363,15 +405,15 @@ func (d DiggerExecutor) Plan(prNumber int) error {
363405
}
364406
for _, step := range planSteps {
365407
if step.Action == "init" {
366-
_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
408+
_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
367409
if err != nil {
368410
return fmt.Errorf("error running init: %v", err)
369411
}
370412
}
371413
if step.Action == "plan" {
372414
planArgs := []string{"-out", d.planFileName()}
373415
planArgs = append(planArgs, step.ExtraArgs...)
374-
isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs)
416+
isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs, d.commandEnvVars)
375417
if err != nil {
376418
return fmt.Errorf("error executing plan: %v", err)
377419
}
@@ -435,13 +477,13 @@ func (d DiggerExecutor) Apply(prNumber int) error {
435477

436478
for _, step := range applySteps {
437479
if step.Action == "init" {
438-
_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
480+
_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
439481
if err != nil {
440482
return fmt.Errorf("error running init: %v", err)
441483
}
442484
}
443485
if step.Action == "apply" {
444-
stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename)
486+
stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename, d.commandEnvVars)
445487
applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
446488
comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.lock.LockId()+"**", applyOutput)
447489
d.prManager.PublishComment(prNumber, comment)

pkg/digger/digger_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,20 @@ type MockTerraformExecutor struct {
3030
Commands []RunInfo
3131
}
3232

33-
func (m *MockTerraformExecutor) Init(params []string) (string, string, error) {
33+
func (m *MockTerraformExecutor) Init(params []string, envs map[string]string) (string, string, error) {
3434
m.Commands = append(m.Commands, RunInfo{"Init", strings.Join(params, " "), time.Now()})
3535
return "", "", nil
3636
}
3737

38-
func (m *MockTerraformExecutor) Apply(params []string, plan *string) (string, string, error) {
38+
func (m *MockTerraformExecutor) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
3939
if plan != nil {
4040
params = append(params, *plan)
4141
}
4242
m.Commands = append(m.Commands, RunInfo{"Apply", strings.Join(params, " "), time.Now()})
4343
return "", "", nil
4444
}
4545

46-
func (m *MockTerraformExecutor) Plan(params []string) (bool, string, string, error) {
46+
func (m *MockTerraformExecutor) Plan(params []string, envs map[string]string) (bool, string, string, error) {
4747
m.Commands = append(m.Commands, RunInfo{"Plan", strings.Join(params, " "), time.Now()})
4848
return true, "", "", nil
4949
}

pkg/integration/integration_test.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ func TestHappyPath(t *testing.T) {
388388
// new pr should lock the project
389389
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
390390
assert.NoError(t, err)
391-
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
391+
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
392392
assert.NoError(t, err)
393393
zipManager := utils.Zipper{}
394394
planStorage := &utils.GithubPlanStorage{
@@ -423,7 +423,7 @@ func TestHappyPath(t *testing.T) {
423423
// 'digger plan' comment should trigger terraform execution
424424
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
425425
assert.NoError(t, err)
426-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
426+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
427427
assert.NoError(t, err)
428428
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
429429
assert.NoError(t, err)
@@ -437,7 +437,7 @@ func TestHappyPath(t *testing.T) {
437437
// 'digger apply' comment should trigger terraform execution and unlock the project
438438
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
439439
assert.NoError(t, err)
440-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
440+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
441441
assert.NoError(t, err)
442442
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
443443
assert.NoError(t, err)
@@ -461,7 +461,7 @@ func TestHappyPath(t *testing.T) {
461461

462462
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
463463
assert.NoError(t, err)
464-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
464+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
465465
assert.NoError(t, err)
466466
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
467467
assert.NoError(t, err)
@@ -539,7 +539,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
539539
// no files changed, no locks
540540
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
541541
assert.NoError(t, err)
542-
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
542+
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
543543
assert.NoError(t, err)
544544

545545
zipManager := utils.Zipper{}
@@ -574,7 +574,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
574574
// 'digger plan' comment should trigger terraform execution
575575
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
576576
assert.NoError(t, err)
577-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
577+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
578578
assert.NoError(t, err)
579579
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
580580
assert.NoError(t, err)
@@ -588,7 +588,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
588588
// 'digger apply' comment should trigger terraform execution and unlock the project
589589
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
590590
assert.NoError(t, err)
591-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
591+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
592592
assert.NoError(t, err)
593593
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
594594
assert.NoError(t, err)
@@ -612,7 +612,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
612612

613613
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
614614
assert.NoError(t, err)
615-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
615+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
616616
assert.NoError(t, err)
617617
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
618618
assert.NoError(t, err)
@@ -754,7 +754,7 @@ workflows:
754754
// new pr should lock the project
755755
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
756756
assert.NoError(t, err)
757-
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
757+
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
758758
assert.NoError(t, err)
759759

760760
zipManager := utils.Zipper{}
@@ -790,7 +790,7 @@ workflows:
790790
// 'digger plan' comment should trigger terraform execution
791791
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
792792
assert.NoError(t, err)
793-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
793+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
794794
assert.NoError(t, err)
795795
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
796796
assert.NoError(t, err)
@@ -804,7 +804,7 @@ workflows:
804804
// 'digger apply' comment should trigger terraform execution and unlock the project
805805
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
806806
assert.NoError(t, err)
807-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
807+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
808808
assert.NoError(t, err)
809809
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
810810
assert.NoError(t, err)
@@ -827,7 +827,7 @@ workflows:
827827

828828
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
829829
assert.NoError(t, err)
830-
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
830+
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
831831
assert.NoError(t, err)
832832
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
833833
assert.NoError(t, err)

0 commit comments

Comments
 (0)