Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add env variable support #209

Merged
merged 20 commits into from
May 9, 2023
12 changes: 12 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 EnvConfig 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 {
Envs Envs `yaml:"envs"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call it EnvVars? just to avoid confusion with any kind of "environments"

Plan *Stage `yaml:"plan,omitempty"`
Apply *Stage `yaml:"apply,omitempty"`
Configuration *WorkflowConfiguration `yaml:"workflow_configuration"`
}

type Envs struct {
State []EnvConfig `yaml:"state"`
Commands []EnvConfig `yaml:"commands"`
}

type DirWalker interface {
GetDirs(workingDir string) ([]string, error)
}
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.StateEnvs,
projectCommands.CommandEnvs,
projectCommands.ApplyStage,
projectCommands.PlanStage,
commandRunner,
Expand Down Expand Up @@ -186,6 +187,8 @@ type ProjectCommand struct {
Commands []string
ApplyStage *configuration.Stage
PlanStage *configuration.Stage
StateEnvs map[string]string
CommandEnvs 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()
}

stateEnvs, commandEnvs := collectEnvs(workflow.Envs)

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,
CommandEnvs: commandEnvs,
StateEnvs: stateEnvs,
})
} 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,
CommandEnvs: commandEnvs,
StateEnvs: stateEnvs,
})
} 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,
CommandEnvs: commandEnvs,
StateEnvs: stateEnvs,
})
}
}
Expand All @@ -243,6 +255,9 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
if !ok {
workflow = *defaultWorkflow()
}

stateEnvs, commandEnvs := collectEnvs(workflow.Envs)

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,
CommandEnvs: commandEnvs,
StateEnvs: stateEnvs,
})
}
}
Expand All @@ -269,6 +286,29 @@ func ConvertGithubEventToCommands(event models.Event, impactedProjects []configu
}
}

func collectEnvs(envs configuration.Envs) (map[string]string, map[string]string) {
stateEnvs := map[string]string{}

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

commandEnvs := map[string]string{}

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

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
stateEnvs map[string]string
commandEnvs 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.stateEnvs)
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.commandEnvs)
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.stateEnvs)
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.commandEnvs)
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
53 changes: 32 additions & 21 deletions pkg/terraform/tf.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
)

type TerraformExecutor interface {
Init([]string) (string, string, error)
Apply([]string, *string) (string, string, error)
Plan([]string) (bool, string, string, error)
Init([]string, map[string]string) (string, string, error)
Apply([]string, *string, map[string]string) (string, string, error)
Plan([]string, map[string]string) (bool, string, string, error)
}

type Terragrunt struct {
Expand All @@ -24,27 +24,27 @@ type Terraform struct {
Workspace string
}

func (terragrunt Terragrunt) Init(params []string) (string, string, error) {
return terragrunt.runTerragruntCommand("init", params...)
func (terragrunt Terragrunt) Init(params []string, envs map[string]string) (string, string, error) {
return terragrunt.runTerragruntCommand("init", envs, params...)

}

func (terragrunt Terragrunt) Apply(params []string, plan *string) (string, string, error) {
func (terragrunt Terragrunt) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
params = append(params, "--auto-approve")
params = append(params, "--terragrunt-non-interactive")
if plan != nil {
params = append(params, *plan)
}
stdout, stderr, err := terragrunt.runTerragruntCommand("apply", params...)
stdout, stderr, err := terragrunt.runTerragruntCommand("apply", envs, params...)
return stdout, stderr, err
}

func (terragrunt Terragrunt) Plan(params []string) (bool, string, string, error) {
stdout, stderr, err := terragrunt.runTerragruntCommand("plan", params...)
func (terragrunt Terragrunt) Plan(params []string, envs map[string]string) (bool, string, string, error) {
stdout, stderr, err := terragrunt.runTerragruntCommand("plan", envs, params...)
return true, stdout, stderr, err
}

func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string) (string, string, error) {
func (terragrunt Terragrunt) runTerragruntCommand(command string, envs map[string]string, arg ...string) (string, string, error) {
args := []string{command}
args = append(args, arg...)
args = append(args, "--terragrunt-working-dir", terragrunt.WorkingDir)
Expand All @@ -53,6 +53,11 @@ func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string)
env := os.Environ()
env = append(env, "TF_CLI_ARGS=-no-color")
env = append(env, "TF_IN_AUTOMATION=true")

for k, v := range envs {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}

cmd.Env = env

var stdout, stderr bytes.Buffer
Expand All @@ -69,23 +74,23 @@ func (terragrunt Terragrunt) runTerragruntCommand(command string, arg ...string)
return stdout.String(), stderr.String(), err
}

func (tf Terraform) Init(params []string) (string, string, error) {
func (tf Terraform) Init(params []string, envs map[string]string) (string, string, error) {
params = append(params, "-upgrade=true")
params = append(params, "-input=false")
params = append(params, "-no-color")
stdout, stderr, _, err := tf.runTerraformCommand("init", params...)
stdout, stderr, _, err := tf.runTerraformCommand("init", envs, params...)
return stdout, stderr, err
}

func (tf Terraform) Apply(params []string, plan *string) (string, string, error) {
workspace, _, _, err := tf.runTerraformCommand("workspace", "show")
func (tf Terraform) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
workspace, _, _, err := tf.runTerraformCommand("workspace", envs, "show")

if err != nil {
return "", "", err
}

if strings.TrimSpace(workspace) != tf.Workspace {
_, _, _, err = tf.runTerraformCommand("workspace", "new", tf.Workspace)
_, _, _, err = tf.runTerraformCommand("workspace", envs, "new", tf.Workspace)
if err != nil {
return "", "", err
}
Expand All @@ -94,14 +99,14 @@ func (tf Terraform) Apply(params []string, plan *string) (string, string, error)
if plan != nil {
params = append(params, *plan)
}
stdout, stderr, _, err := tf.runTerraformCommand("apply", params...)
stdout, stderr, _, err := tf.runTerraformCommand("apply", envs, params...)
if err != nil {
return "", "", err
}
return stdout, stderr, nil
}

func (tf Terraform) runTerraformCommand(command string, arg ...string) (string, string, int, error) {
func (tf Terraform) runTerraformCommand(command string, envs map[string]string, arg ...string) (string, string, int, error) {
args := []string{"-chdir=" + tf.WorkingDir}
args = append(args, command)
args = append(args, arg...)
Expand All @@ -113,6 +118,12 @@ func (tf Terraform) runTerraformCommand(command string, arg ...string) (string,

cmd := exec.Command("terraform", args...)

env := os.Environ()
for k, v := range envs {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
cmd.Env = env

cmd.Stdout = mwout
cmd.Stderr = mwerr

Expand Down Expand Up @@ -145,20 +156,20 @@ func (sw *StdWriter) GetString() string {
return s
}

func (tf Terraform) Plan(params []string) (bool, string, string, error) {
workspace, _, _, err := tf.runTerraformCommand("workspace", "show")
func (tf Terraform) Plan(params []string, envs map[string]string) (bool, string, string, error) {
workspace, _, _, err := tf.runTerraformCommand("workspace", envs, "show")

if err != nil {
return false, "", "", err
}
if strings.TrimSpace(workspace) != tf.Workspace {
_, _, _, err = tf.runTerraformCommand("workspace", "new", tf.Workspace)
_, _, _, err = tf.runTerraformCommand("workspace", envs, "new", tf.Workspace)
if err != nil {
return false, "", "", err
}
}
params = append(append(params, "-input=false"), "-no-color")
stdout, stderr, statusCode, err := tf.runTerraformCommand("plan", params...)
stdout, stderr, statusCode, err := tf.runTerraformCommand("plan", envs, params...)
if err != nil {
return false, "", "", err
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/terraform/tf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func TestExecuteTerraformPlan(t *testing.T) {
CreateValidTerraformTestFile(dir)

tf := Terraform{WorkingDir: dir, Workspace: "dev"}
tf.Init([]string{})
_, _, _, err := tf.Plan([]string{})
tf.Init([]string{}, map[string]string{})
_, _, _, err := tf.Plan([]string{}, map[string]string{})
assert.NoError(t, err)
}

Expand All @@ -36,8 +36,8 @@ func TestExecuteTerraformApply(t *testing.T) {
CreateValidTerraformTestFile(dir)

tf := Terraform{WorkingDir: dir, Workspace: "dev"}
tf.Init([]string{})
_, _, _, err := tf.Plan([]string{})
tf.Init([]string{}, map[string]string{})
_, _, _, err := tf.Plan([]string{}, map[string]string{})
assert.NoError(t, err)
}

Expand All @@ -53,11 +53,11 @@ func TestExecuteTerraformApplyDefaultWorkspace(t *testing.T) {
CreateValidTerraformTestFile(dir)

tf := Terraform{WorkingDir: dir, Workspace: "default"}
tf.Init([]string{})
tf.Init([]string{}, map[string]string{})
var planArgs []string
planArgs = append(planArgs, "-out", "plan.tfplan")
tf.Plan(planArgs)
tf.Plan(planArgs, map[string]string{})
plan := "plan.tfplan"
_, _, err := tf.Apply([]string{}, &plan)
_, _, err := tf.Apply([]string{}, &plan, map[string]string{})
assert.NoError(t, err)
}