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

Fixes issues with AWSAssumeRole in Blocks for Terraform being passed in #1720

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions libs/digger_config/digger_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,22 @@ func HandleYamlProjectGeneration(config *DiggerConfigYaml, terraformDir string,
workflow = b.Workflow
}

err := hydrateDiggerConfigYamlWithTerragrunt(config, TerragruntParsingConfig{
tgParsingConfig := TerragruntParsingConfig{
CreateProjectName: true,
DefaultWorkflow: workflow,
WorkflowFile: b.WorkflowFile,
FilterPath: path.Join(terraformDir, *b.RootDir),
}, terraformDir)
};

// allow blocks to pass in roles that can be assummed by aws
tgParsingConfig.AwsRoleToAssume = b.AwsRoleToAssume
Copy link
Contributor

Choose a reason for hiding this comment

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

sorry another nit, why not just pass it in the struct above directly? :D



err := hydrateDiggerConfigYamlWithTerragrunt(config, tgParsingConfig, terraformDir)
if err != nil {
return err
}

}
} else {
includePatterns = []string{b.Include}
Expand Down Expand Up @@ -500,6 +507,7 @@ func hydrateDiggerConfigYamlWithTerragrunt(configYaml *DiggerConfigYaml, parsing
WorkflowFile: &workflowFile,
IncludePatterns: atlantisProject.Autoplan.WhenModified,
Generated: true,
AwsRoleToAssume: parsingConfig.AwsRoleToAssume,
})
}
return nil
Expand Down
1 change: 1 addition & 0 deletions libs/digger_config/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ type TerragruntParsingConfig struct {
UseProjectMarkers bool `yaml:"useProjectMarkers"`
ExecutionOrderGroups *bool `yaml:"executionOrderGroups"`
WorkflowFile string `yaml:"workflow_file"`
AwsRoleToAssume *AssumeRoleForProjectConfig `yaml:"aws_role_to_assume,omitempty"`
}

func (p *ProjectYaml) UnmarshalYAML(unmarshal func(interface{}) error) error {
Expand Down
14 changes: 14 additions & 0 deletions libs/execution/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"path"
"regexp"
"runtime"
"strconv"
"strings"

Expand Down Expand Up @@ -217,6 +218,7 @@ func (d DiggerExecutor) Plan() (*terraform_utils.TerraformSummary, bool, bool, s
}
}
for _, step := range planSteps {
log.Printf(" Running step: %v\n", step.Action)
if step.Action == "init" {
_, stderr, err := d.TerraformExecutor.Init(step.ExtraArgs, d.StateEnvVars)
if err != nil {
Expand Down Expand Up @@ -531,3 +533,15 @@ func cleanupTerraformPlan(nonEmptyPlan bool, planError error, stdout string, std
func (d DiggerExecutor) projectId() string {
return d.ProjectNamespace + "#" + d.ProjectName
}

// this will log an exit code and error based on the executor of the executor drivers are by filename
func logCommandFail(exitCode int, err error) {

_, filename, _, ok := runtime.Caller(1);
if ok {
executor := strings.TrimSuffix(path.Base(filename), path.Ext(filename))
log.Printf("Command failed in %v with exit code %v and error %v", executor, exitCode, err)
} else {
log.Printf("Command failed in unknown executor with exit code %v and error %v", exitCode, err)
}
}
Comment on lines +538 to +547
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks so much for this improvement, I remember terragrunt jobs always had obscure error messages especially when they failed

73 changes: 52 additions & 21 deletions libs/execution/terragrunt.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
)

type Terragrunt struct {
WorkingDir string
}

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

stdout, stderr, exitCode, err := terragrunt.runTerragruntCommand("init", true, envs, params...)
if exitCode != 0 {
logCommandFail(exitCode, err)
}

return stdout, stderr, err
}

func (terragrunt Terragrunt) Apply(params []string, plan *string, envs map[string]string) (string, string, error) {
Expand All @@ -23,41 +30,58 @@ func (terragrunt Terragrunt) Apply(params []string, plan *string, envs map[strin
if plan != nil {
params = append(params, *plan)
}
stdout, stderr, err := terragrunt.runTerragruntCommand("apply", true, envs, params...)
stdout, stderr, exitCode, err := terragrunt.runTerragruntCommand("apply", true, envs, params...)
if exitCode != 0 {
logCommandFail(exitCode, err)
}

return stdout, stderr, err
}

func (terragrunt Terragrunt) Destroy(params []string, envs map[string]string) (string, string, error) {
params = append(params, "--auto-approve")
params = append(params, "--terragrunt-non-interactive")
stdout, stderr, err := terragrunt.runTerragruntCommand("destroy", true, envs, params...)
stdout, stderr, exitCode, err := terragrunt.runTerragruntCommand("destroy", true, envs, params...)
if exitCode != 0 {
logCommandFail(exitCode, err)
}


return stdout, stderr, err
}

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

return true, stdout, stderr, err
}

func (terragrunt Terragrunt) Show(params []string, envs map[string]string) (string, string, error) {
stdout, stderr, err := terragrunt.runTerragruntCommand("show", false, envs, params...)
stdout, stderr, exitCode, err := terragrunt.runTerragruntCommand("show", false, envs, params...)
if exitCode != 0 {
logCommandFail(exitCode, err)
}

return stdout, stderr, err
}

func (terragrunt Terragrunt) runTerragruntCommand(command string, printOutputToStdout bool, envs map[string]string, arg ...string) (string, string, error) {
func (terragrunt Terragrunt) runTerragruntCommand(command string, printOutputToStdout bool, envs map[string]string, arg ...string) (stdOut string, stdErr string, exitCode int, err error) {
args := []string{command}
args = append(args, arg...)
cmd := exec.Command("terragrunt", args...)
cmd.Dir = terragrunt.WorkingDir

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))
expandedArgs := make([]string, 0)
for _, p := range args {
s := os.ExpandEnv(p)
s = strings.TrimSpace(s)
if s != "" {
expandedArgs = append(expandedArgs, s)
}
}

// Set up common output buffers
var mwout, mwerr io.Writer
var stdout, stderr bytes.Buffer
if printOutputToStdout {
Expand All @@ -68,15 +92,22 @@ func (terragrunt Terragrunt) runTerragruntCommand(command string, printOutputToS
mwerr = io.Writer(&stderr)
}

cmd.Env = env
cmd.Stdout = mwout
cmd.Stderr = mwerr
cmd := exec.Command("terragrunt", args...)
log.Printf("Running command: terragrunt %v", expandedArgs)
cmd.Dir = terragrunt.WorkingDir

err := cmd.Run()
env := os.Environ()
env = append(env, "TF_CLI_ARGS=-no-color")
env = append(env, "TF_IN_AUTOMATION=true")

if err != nil {
return stdout.String(), stderr.String(), fmt.Errorf("error: %v", err)
for k, v := range envs {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}

return stdout.String(), stderr.String(), err
cmd.Env = env
cmd.Stdout = mwout
cmd.Stderr = mwerr

err = cmd.Run()
return stdout.String(), stderr.String(), cmd.ProcessState.ExitCode(), err
}
52 changes: 36 additions & 16 deletions libs/scheduler/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,34 @@ func (job *Job) PopulateAwsCredentialsEnvVarsForJob() error {
log.Printf("Project-level AWS role detected, Assuming role for project: %v", job.ProjectName)
var err error
backendConfigArgs, err := populateretrieveBackendConfigArgs(*job.StateEnvProvider)
if err != nil {
log.Printf("Failed to get keys from role: %v", err)
return fmt.Errorf("Failed to get (state) keys from role: %v", err)
}

if job.PlanStage != nil {
// TODO: check that the first step is infact the terraform "init" step
job.PlanStage.Steps[0].ExtraArgs = append(job.PlanStage.Steps[0].ExtraArgs, backendConfigArgs...)
}
if job.ApplyStage != nil {
// TODO: check that the first step is infact the terraform "init" step
job.ApplyStage.Steps[0].ExtraArgs = append(job.ApplyStage.Steps[0].ExtraArgs, backendConfigArgs...)
}
if err != nil {
log.Printf("Failed to get keys from role: %v", err)
return fmt.Errorf("Failed to get (state) keys from role: %v", err)
// Terragrunt will cause a backend configuration problem if backend-config options are passed and envs of the same key are passed.
// which will trigger a request to init with --reconfigure, so do not use backend-config for terragrunt
if job.Terragrunt != true {
Comment on lines +49 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

today I learned something new, so with terragrunt does it pass those options to terraform based on terragrunt.hcl configuration or what is the source of the duplicates?

Copy link
Contributor Author

@ben-of-codecraft ben-of-codecraft Sep 23, 2024

Choose a reason for hiding this comment

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

From the docs I read it should not happen but in practice I tested it out and it consistently happened, if I set aws credentials in the my environment and then also pass in backend-config options for credentials causes that request on state change when it runs the plan.

The init would work fine with both backend-config and environment variables.
When the plan would run, since backend-config variables were passed only into the init step it would detect a remote change even though the backend file was the same (must add some hash or something of the complete config in terragrunt / terraform somewhere)

So the option was to make it pass backend-config to all steps or just exclude it. I chose to exclude it path, as it does not seem to impact terraform. backend-config is not recommended on the TF site, I figured it was best at least for TG to just ignore their use.

I did not go much deeper than that, but I know the above does work.

if err != nil {
log.Printf("Failed to get keys from role: %v", err)
return fmt.Errorf("Failed to get (state) keys from role: %v", err)
}

if job.PlanStage != nil {
// TODO: check that the first step is infact the terraform "init" step
job.PlanStage.Steps[0].ExtraArgs = append(job.PlanStage.Steps[0].ExtraArgs, backendConfigArgs...)
}
if job.ApplyStage != nil {
// TODO: check that the first step is infact the terraform "init" step
job.ApplyStage.Steps[0].ExtraArgs = append(job.ApplyStage.Steps[0].ExtraArgs, backendConfigArgs...)
}
if err != nil {
log.Printf("Failed to get keys from role: %v", err)
return fmt.Errorf("Failed to get (state) keys from role: %v", err)
}
} else {
job.StateEnvVars, err = populateKeys(job.StateEnvVars, *job.StateEnvProvider)
if err != nil {
log.Printf("Failed to get keys from role (StateEnvProvider): %v", err)
return fmt.Errorf("Failed to get (state) keys from role: %v", err)
}
}

}

if job.CommandEnvProvider != nil {
Expand All @@ -73,6 +83,16 @@ func (job *Job) PopulateAwsCredentialsEnvVarsForJob() error {
return fmt.Errorf("Failed to get (command) keys from role: %v", err)
}
}

// If state environment variables are not set them to match command env vars
if len(job.StateEnvVars) == 0 && len(job.CommandEnvVars) != 0 {
job.StateEnvVars = job.CommandEnvVars
}

if len(job.StateEnvVars) != 0 && len(job.CommandEnvVars) == 0 {
job.CommandEnvVars = job.StateEnvVars
}

return nil
}

Expand Down
Loading