Skip to content

Commit

Permalink
feat: add env variable support (diggerhq#209)
Browse files Browse the repository at this point in the history
* feat: add env variable support
  • Loading branch information
Spartakovic authored May 9, 2023
1 parent 2881cfc commit 5bd074a
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 59 deletions.
10 changes: 0 additions & 10 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,6 @@ outputs:
runs:
using: composite
steps:
- name: Validate General Input Configuration
run: |
if [[ "${{ inputs.setup-aws }}" == "false" && "${{ inputs.setup-google-cloud }}" == "false" ]]; then
echo "Either 'setup-aws' or 'setup-google-cloud' input must be set to 'true'"
else
exit 0
fi
exit 1
shell: bash

- name: Validate Input Configuration for Google
run: |
if [[ -z ${{ toJSON(inputs.google-auth-credentials) }} && -z "${{ inputs.google-workload-identity-provider }}" ]]; then
Expand Down
16 changes: 16 additions & 0 deletions pkg/configuration/digger_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ type DiggerConfigYaml struct {
GenerateProjectsConfig *GenerateProjectsConfig `yaml:"generate_projects"`
}

type EnvVarConfig struct {
Name string `yaml:"name"`
ValueFrom string `yaml:"value_from"`
Value string `yaml:"value"`
}

type DiggerConfig struct {
Projects []Project
AutoMerge bool
Expand All @@ -50,11 +56,17 @@ type Stage struct {
}

type Workflow struct {
EnvVars EnvVars `yaml:"env_vars"`
Plan *Stage `yaml:"plan,omitempty"`
Apply *Stage `yaml:"apply,omitempty"`
Configuration *WorkflowConfiguration `yaml:"workflow_configuration"`
}

type EnvVars struct {
State []EnvVarConfig `yaml:"state"`
Commands []EnvVarConfig `yaml:"commands"`
}

type DirWalker interface {
GetDirs(workingDir string) ([]string, error)
}
Expand Down Expand Up @@ -125,6 +137,10 @@ func (w *Workflow) UnmarshalYAML(unmarshal func(interface{}) error) error {
},
},
},
EnvVars: EnvVars{
State: []EnvVarConfig{},
Commands: []EnvVarConfig{},
},
}
if err := unmarshal(&raw); err != nil {
return err
Expand Down
50 changes: 50 additions & 0 deletions pkg/configuration/digger_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,56 @@ workflows:
assert.Equal(t, Step{Action: "run", Value: "echo \"hello\""}, dg.Workflows["myworkflow"].Plan.Steps[0], "parsed struct does not match expected struct")
}

func TestEnvVarsConfiguration(t *testing.T) {
tempDir, teardown := setUp()
defer teardown()

diggerCfg := `
projects:
- name: dev
branch: /main/
dir: .
workspace: default
terragrunt: false
workflow: myworkflow
workflows:
myworkflow:
plan:
steps:
- init:
extra_args: ["-lock=false"]
- plan:
extra_args: ["-lock=false"]
- run: echo "hello"
apply:
steps:
- apply:
extra_args: ["-lock=false"]
workflow_configuration:
on_pull_request_pushed: [digger plan]
on_pull_request_closed: [digger unlock]
on_commit_to_default: [digger apply]
env_vars:
state:
- name: TF_VAR_state
value: s3://mybucket/terraform.tfstate
commands:
- name: TF_VAR_command
value: plan
`
deleteFile := createFile(path.Join(tempDir, "digger.yaml"), diggerCfg)
defer deleteFile()

dg, err := NewDiggerConfig(tempDir, &FileSystemDirWalker{})
assert.NoError(t, err, "expected error to be nil")
assert.Equal(t, []EnvVarConfig{
{Name: "TF_VAR_state", Value: "s3://mybucket/terraform.tfstate"},
}, dg.Workflows["myworkflow"].EnvVars.State, "parsed struct does not match expected struct")
assert.Equal(t, []EnvVarConfig{
{Name: "TF_VAR_command", Value: "plan"},
}, dg.Workflows["myworkflow"].EnvVars.Commands, "parsed struct does not match expected struct")
}

func TestDefaultValuesForWorkflowConfiguration(t *testing.T) {
tempDir, teardown := setUp()
defer teardown()
Expand Down
52 changes: 47 additions & 5 deletions pkg/digger/digger.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ func RunCommandsPerProject(commandsPerProject []ProjectCommand, repoOwner string
}

commandRunner := CommandRunner{}

diggerExecutor := DiggerExecutor{
projectCommands.ProjectName,
projectCommands.StateEnvVars,
projectCommands.CommandEnvVars,
projectCommands.ApplyStage,
projectCommands.PlanStage,
commandRunner,
Expand Down Expand Up @@ -186,6 +187,8 @@ type ProjectCommand struct {
Commands []string
ApplyStage *configuration.Stage
PlanStage *configuration.Stage
StateEnvVars map[string]string
CommandEnvVars map[string]string
}

func ConvertGithubEventToCommands(event models.Event, impactedProjects []configuration.Project, workflows map[string]configuration.Workflow) ([]ProjectCommand, error) {
Expand All @@ -199,6 +202,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
if !ok {
workflow = *defaultWorkflow()
}

stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)

if event.Action == "closed" && event.PullRequest.Merged && event.PullRequest.Base.Ref == event.Repository.DefaultBranch {
commandsPerProject = append(commandsPerProject, ProjectCommand{
ProjectName: project.Name,
Expand All @@ -208,6 +214,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
Commands: workflow.Configuration.OnCommitToDefault,
ApplyStage: workflow.Apply,
PlanStage: workflow.Plan,
CommandEnvVars: commandEnvVars,
StateEnvVars: stateEnvVars,
})
} else if event.Action == "opened" || event.Action == "reopened" || event.Action == "synchronize" {
commandsPerProject = append(commandsPerProject, ProjectCommand{
Expand All @@ -218,6 +226,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
Commands: workflow.Configuration.OnPullRequestPushed,
ApplyStage: workflow.Apply,
PlanStage: workflow.Plan,
CommandEnvVars: commandEnvVars,
StateEnvVars: stateEnvVars,
})
} else if event.Action == "closed" {
commandsPerProject = append(commandsPerProject, ProjectCommand{
Expand All @@ -228,6 +238,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
Commands: workflow.Configuration.OnPullRequestClosed,
ApplyStage: workflow.Apply,
PlanStage: workflow.Plan,
CommandEnvVars: commandEnvVars,
StateEnvVars: stateEnvVars,
})
}
}
Expand All @@ -243,6 +255,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
if !ok {
workflow = *defaultWorkflow()
}

stateEnvVars, commandEnvVars := collectEnvVars(workflow.EnvVars)

workspace := project.Workspace
workspaceOverride, err := parseWorkspace(event.Comment.Body)
if err != nil {
Expand All @@ -259,6 +274,8 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
Commands: []string{command},
ApplyStage: workflow.Apply,
PlanStage: workflow.Plan,
CommandEnvVars: commandEnvVars,
StateEnvVars: stateEnvVars,
})
}
}
Expand All @@ -269,6 +286,29 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
}
}

func collectEnvVars(envs configuration.EnvVars) (map[string]string, map[string]string) {
stateEnvVars := map[string]string{}

for _, envvar := range envs.State {
if envvar.Value != "" {
stateEnvVars[envvar.Name] = envvar.Value
} else if envvar.ValueFrom != "" {
stateEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
}
}

commandEnvVars := map[string]string{}

for _, envvar := range envs.Commands {
if envvar.Value != "" {
commandEnvVars[envvar.Name] = envvar.Value
} else if envvar.ValueFrom != "" {
commandEnvVars[envvar.Name] = os.Getenv(envvar.ValueFrom)
}
}
return stateEnvVars, commandEnvVars
}

func parseWorkspace(comment string) (string, error) {
re := regexp.MustCompile(`-w(?:\s+(\S+)|$)`)
matches := re.FindAllStringSubmatch(comment, -1)
Expand Down Expand Up @@ -299,6 +339,8 @@ func parseProjectName(comment string) string {

type DiggerExecutor struct {
projectName string
stateEnvVars map[string]string
commandEnvVars map[string]string
applyStage *configuration.Stage
planStage *configuration.Stage
commandRunner CommandRun
Expand Down Expand Up @@ -363,15 +405,15 @@ func (d DiggerExecutor) Plan(prNumber int) error {
}
for _, step := range planSteps {
if step.Action == "init" {
_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
if err != nil {
return fmt.Errorf("error running init: %v", err)
}
}
if step.Action == "plan" {
planArgs := []string{"-out", d.planFileName()}
planArgs = append(planArgs, step.ExtraArgs...)
isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs)
isNonEmptyPlan, stdout, stderr, err := d.terraformExecutor.Plan(planArgs, d.commandEnvVars)
if err != nil {
return fmt.Errorf("error executing plan: %v", err)
}
Expand Down Expand Up @@ -435,13 +477,13 @@ func (d DiggerExecutor) Apply(prNumber int) error {

for _, step := range applySteps {
if step.Action == "init" {
_, _, err := d.terraformExecutor.Init(step.ExtraArgs)
_, _, err := d.terraformExecutor.Init(step.ExtraArgs, d.stateEnvVars)
if err != nil {
return fmt.Errorf("error running init: %v", err)
}
}
if step.Action == "apply" {
stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename)
stdout, stderr, err := d.terraformExecutor.Apply(step.ExtraArgs, plansFilename, d.commandEnvVars)
applyOutput := cleanupTerraformApply(true, err, stdout, stderr)
comment := utils.GetTerraformOutputAsCollapsibleComment("Apply for **"+d.lock.LockId()+"**", applyOutput)
d.prManager.PublishComment(prNumber, comment)
Expand Down
6 changes: 3 additions & 3 deletions pkg/digger/digger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ type MockTerraformExecutor struct {
Commands []RunInfo
}

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

func (m *MockTerraformExecutor) Apply(params []string, plan *string) (string, string, error) {
func (m *MockTerraformExecutor) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
if plan != nil {
params = append(params, *plan)
}
m.Commands = append(m.Commands, RunInfo{"Apply", strings.Join(params, " "), time.Now()})
return "", "", nil
}

func (m *MockTerraformExecutor) Plan(params []string) (bool, string, string, error) {
func (m *MockTerraformExecutor) Plan(params []string, envs map[string]string) (bool, string, string, error) {
m.Commands = append(m.Commands, RunInfo{"Plan", strings.Join(params, " "), time.Now()})
return true, "", "", nil
}
Expand Down
24 changes: 12 additions & 12 deletions pkg/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ func TestHappyPath(t *testing.T) {
// new pr should lock the project
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
zipManager := utils.Zipper{}
planStorage := &utils.GithubPlanStorage{
Expand Down Expand Up @@ -423,7 +423,7 @@ func TestHappyPath(t *testing.T) {
// 'digger plan' comment should trigger terraform execution
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -437,7 +437,7 @@ func TestHappyPath(t *testing.T) {
// 'digger apply' comment should trigger terraform execution and unlock the project
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -461,7 +461,7 @@ func TestHappyPath(t *testing.T) {

impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand Down Expand Up @@ -539,7 +539,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
// no files changed, no locks
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)

zipManager := utils.Zipper{}
Expand Down Expand Up @@ -574,7 +574,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
// 'digger plan' comment should trigger terraform execution
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -588,7 +588,7 @@ func TestMultiEnvHappyPath(t *testing.T) {
// 'digger apply' comment should trigger terraform execution and unlock the project
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -612,7 +612,7 @@ func TestMultiEnvHappyPath(t *testing.T) {

impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, &dynamoDbLock, planStorage, dir)
assert.NoError(t, err)
Expand Down Expand Up @@ -754,7 +754,7 @@ workflows:
// new pr should lock the project
impactedProjects, prNumber, _, err := digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err := digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)

zipManager := utils.Zipper{}
Expand Down Expand Up @@ -790,7 +790,7 @@ workflows:
// 'digger plan' comment should trigger terraform execution
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -804,7 +804,7 @@ workflows:
// 'digger apply' comment should trigger terraform execution and unlock the project
impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand All @@ -827,7 +827,7 @@ workflows:

impactedProjects, prNumber, _, err = digger.ProcessGitHubEvent(ghEvent, diggerConfig, githubPrService)
assert.NoError(t, err)
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, map[string]configuration.Workflow{})
commandsToRunPerProject, err = digger.ConvertGithubEventToCommands(ghEvent, impactedProjects, diggerConfig.Workflows)
assert.NoError(t, err)
_, err = digger.RunCommandsPerProject(commandsToRunPerProject, repoOwner, repositoryName, eventName, prNumber, githubPrService, lock, planStorage, dir)
assert.NoError(t, err)
Expand Down
Loading

0 comments on commit 5bd074a

Please sign in to comment.