Skip to content

Commit 4b47cca

Browse files
yhakbarRahul-Kumar-prog
authored andcommitted
fix: Removing extra --auth-provider-cmd call (gruntwork-io#6045)
* fix: Removing extra `--auth-provider-cmd` call * chore: Adding `TestReadTerragruntAuthProviderCmdRunAllCallCountWithRacing`
1 parent 3f9b969 commit 4b47cca

9 files changed

Lines changed: 139 additions & 55 deletions

File tree

pkg/config/dependency.go

Lines changed: 16 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,12 +1051,13 @@ func getTerragruntOutputJSON(ctx context.Context, pctx *ParsingContext, l log.Lo
10511051
}
10521052

10531053
if isInit {
1054-
credsGetter := creds.NewGetter()
1055-
if err = credsGetter.ObtainAndUpdateEnvIfNecessary(
1054+
mergedIAM := iam.MergeRoleOptions(remoteStateTGConfig.GetIAMRoleOptions(), pctx.OriginalIAMRoleOptions)
1055+
if err = creds.NewGetter().ObtainAndUpdateEnvIfNecessary(
10561056
ctx,
10571057
l,
10581058
pctx.Env,
10591059
externalcmd.NewProvider(l, pctx.AuthProviderCmd, shellRunOptsFromPctx(pctx)),
1060+
amazonsts.NewProvider(l, mergedIAM, pctx.Env),
10601061
); err != nil {
10611062
return nil, err
10621063
}
@@ -1066,8 +1067,6 @@ func getTerragruntOutputJSON(ctx context.Context, pctx *ParsingContext, l log.Lo
10661067
pctx,
10671068
l,
10681069
workingDir,
1069-
remoteStateTGConfig.GetIAMRoleOptions(),
1070-
credsGetter,
10711070
)
10721071
}
10731072

@@ -1120,28 +1119,17 @@ func terragruntAlreadyInit(ctx context.Context, l log.Logger, pctx *ParsingConte
11201119
}
11211120

11221121
// getTerragruntOutputJSONFromInitFolder will retrieve the outputs directly from the module's working directory without
1123-
// running init.
1122+
// running init. Callers must populate pctx.Env with auth-provider-cmd credentials and any
1123+
// TG_IAM_ROLE assumption beforehand.
11241124
func getTerragruntOutputJSONFromInitFolder(
11251125
ctx context.Context,
11261126
pctx *ParsingContext,
11271127
l log.Logger,
11281128
terraformWorkingDir string,
1129-
iamRoleOpts iam.RoleOptions,
1130-
credsGetter *creds.Getter,
11311129
) ([]byte, error) {
11321130
targetConfigPath := pctx.TerragruntConfigPath
11331131

1134-
tfRunOpts, err := setupTFRunOptsForBareTerraform(
1135-
ctx,
1136-
pctx,
1137-
l,
1138-
terraformWorkingDir,
1139-
iamRoleOpts,
1140-
credsGetter,
1141-
)
1142-
if err != nil {
1143-
return nil, err
1144-
}
1132+
tfRunOpts := setupTFRunOptsForBareTerraform(pctx, terraformWorkingDir)
11451133

11461134
l.Debugf(
11471135
"Unit '%s' is already init-ed. "+
@@ -1216,27 +1204,18 @@ func getTerragruntOutputJSONFromRemoteState(
12161204

12171205
l.Debugf("Setting dependency working directory to %s", tempWorkDir)
12181206

1219-
credsGetter := creds.NewGetter()
1220-
if err = credsGetter.ObtainAndUpdateEnvIfNecessary(
1207+
mergedIAM := iam.MergeRoleOptions(iamRoleOpts, pctx.OriginalIAMRoleOptions)
1208+
if err = creds.NewGetter().ObtainAndUpdateEnvIfNecessary(
12211209
ctx,
12221210
l,
12231211
pctx.Env,
12241212
externalcmd.NewProvider(l, pctx.AuthProviderCmd, shellRunOptsFromPctx(pctx)),
1213+
amazonsts.NewProvider(l, mergedIAM, pctx.Env),
12251214
); err != nil {
12261215
return nil, err
12271216
}
12281217

1229-
tfRunOpts, err := setupTFRunOptsForBareTerraform(
1230-
ctx,
1231-
pctx,
1232-
l,
1233-
tempWorkDir,
1234-
iamRoleOpts,
1235-
credsGetter,
1236-
)
1237-
if err != nil {
1238-
return nil, err
1239-
}
1218+
tfRunOpts := setupTFRunOptsForBareTerraform(pctx, tempWorkDir)
12401219

12411220
// To speed up dependencies processing it is possible to retrieve its output directly from the backend without init dependencies
12421221
if pctx.Experiments.Evaluate(experiment.DependencyFetchOutputFromState) && !pctx.NoDependencyFetchOutputFromState {
@@ -1361,38 +1340,20 @@ func getTerragruntOutputJSONFromRemoteStateS3(ctx context.Context, l log.Logger,
13611340
return jsonOutputs, nil
13621341
}
13631342

1364-
// setupTFRunOptsForBareTerraform builds a *tf.RunOptions that can be used to run terraform
1365-
// without going through the full RunTerragrunt operation. It merges IAM roles and obtains
1366-
// credentials inline.
1367-
func setupTFRunOptsForBareTerraform(
1368-
ctx context.Context,
1369-
pctx *ParsingContext,
1370-
l log.Logger,
1371-
workingDir string,
1372-
iamRoleOpts iam.RoleOptions,
1373-
credsGetter *creds.Getter,
1374-
) (*tf.TFOptions, error) {
1375-
// Merge IAM options
1376-
mergedIAM := iam.MergeRoleOptions(iamRoleOpts, pctx.OriginalIAMRoleOptions)
1377-
1378-
// Build shell.RunOptions for this specific working dir with io.Discard as writer
1343+
// setupTFRunOptsForBareTerraform builds a *tf.TFOptions for running terraform without
1344+
// going through the full RunTerragrunt operation. Callers must obtain credentials
1345+
// (auth-provider-cmd and any TG_IAM_ROLE assumption) before invoking, so those steps
1346+
// run from the unit's directory rather than the bare-terraform working directory.
1347+
func setupTFRunOptsForBareTerraform(pctx *ParsingContext, workingDir string) *tf.TFOptions {
13791348
shellOpts := shellRunOptsFromPctx(pctx)
13801349
shellOpts.WorkingDir = workingDir
13811350
shellOpts.Writers.Writer = io.Discard
13821351

1383-
// Make sure to assume any roles set by TG_IAM_ROLE
1384-
if err := credsGetter.ObtainAndUpdateEnvIfNecessary(ctx, l, pctx.Env,
1385-
externalcmd.NewProvider(l, pctx.AuthProviderCmd, shellOpts),
1386-
amazonsts.NewProvider(l, mergedIAM, pctx.Env),
1387-
); err != nil {
1388-
return nil, err
1389-
}
1390-
13911352
return &tf.TFOptions{
13921353
JSONLogFormat: pctx.JSONLogFormat,
13931354
OriginalTerragruntConfigPath: pctx.OriginalTerragruntConfigPath,
13941355
ShellOptions: shellOpts,
1395-
}, nil
1356+
}
13961357
}
13971358

13981359
// runTerragruntOutputJSON uses terragrunt running functions to extract the json output from the target config.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.jsonl
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
3+
# Records each invocation as a JSON line in calls.jsonl in this script's
4+
# directory. Tests read calls.jsonl to assert how many times the
5+
# --auth-provider-cmd was invoked and the working directory of each call.
6+
7+
set -e
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
LOG_FILE="${SCRIPT_DIR}/calls.jsonl"
11+
12+
# JSON escape: replace backslash and double-quote.
13+
escaped_pwd="${PWD//\\/\\\\}"
14+
escaped_pwd="${escaped_pwd//\"/\\\"}"
15+
16+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
17+
18+
printf '{"ts":"%s","working_dir":"%s","pid":%d}\n' "$ts" "$escaped_pwd" "$$" >>"$LOG_FILE"
19+
20+
printf '{"envs":{"AUTH_PROVIDER_CALL_COUNT_VAR":"ok"}}\n'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
variable "name" {
2+
type = string
3+
}
4+
5+
output "value" {
6+
value = "from-${var.name}"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
include {
2+
path = find_in_parent_folders("root.hcl")
3+
}
4+
5+
inputs = {
6+
name = "dep"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
variable "upstream" {
2+
type = string
3+
}
4+
5+
output "value" {
6+
value = var.upstream
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
include {
2+
path = find_in_parent_folders("root.hcl")
3+
}
4+
5+
dependency "dep" {
6+
config_path = "../dep"
7+
}
8+
9+
inputs = {
10+
upstream = dependency.dep.outputs.value
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
remote_state {
2+
backend = "local"
3+
generate = {
4+
path = "backend.tf"
5+
if_exists = "overwrite_terragrunt"
6+
}
7+
config = {
8+
path = "${path_relative_to_include()}/terraform.tfstate"
9+
}
10+
}

test/integration_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3307,6 +3307,66 @@ func TestReadTerragruntAuthProviderCmdEnvInLocalsRunAll(t *testing.T) {
33073307
assert.Equal(t, "from-auth-provider", outputs["secret"].Value)
33083308
}
33093309

3310+
func TestReadTerragruntAuthProviderCmdRunAllCallCountWithRacing(t *testing.T) {
3311+
t.Parallel()
3312+
3313+
helpers.CleanupTerraformFolder(t, testFixtureAuthProviderCmd)
3314+
tmpEnvPath := helpers.CopyEnvironment(t, testFixtureAuthProviderCmd)
3315+
rootPath := filepath.Join(tmpEnvPath, testFixtureAuthProviderCmd, "run-all-call-count")
3316+
authProviderCmd := filepath.Join(rootPath, "auth-provider.sh")
3317+
logPath := filepath.Join(rootPath, "calls.jsonl")
3318+
3319+
helpers.ValidateAuthProviderScript(t, rootPath, authProviderCmd)
3320+
require.NoError(t, os.Remove(logPath), "auth-provider.sh should have created %s", logPath)
3321+
3322+
helpers.RunTerragrunt(
3323+
t,
3324+
fmt.Sprintf(
3325+
"terragrunt run --all apply --non-interactive "+
3326+
"--working-dir %s --auth-provider-cmd %s",
3327+
rootPath,
3328+
authProviderCmd,
3329+
),
3330+
)
3331+
3332+
logBytes, err := os.ReadFile(logPath)
3333+
require.NoError(t, err)
3334+
3335+
type authCall struct {
3336+
Timestamp string `json:"ts"`
3337+
WorkingDir string `json:"working_dir"`
3338+
PID int `json:"pid"`
3339+
}
3340+
3341+
lines := strings.Split(strings.TrimRight(string(logBytes), "\n"), "\n")
3342+
calls := make([]authCall, 0, len(lines))
3343+
3344+
for i, line := range lines {
3345+
var call authCall
3346+
3347+
require.NoErrorf(t, json.Unmarshal([]byte(line), &call), "line %d: %q", i+1, line)
3348+
3349+
calls = append(calls, call)
3350+
}
3351+
3352+
t.Logf("auth-provider-cmd invocations:\n%s", string(logBytes))
3353+
3354+
// Five invocations are expected, in this order:
3355+
//
3356+
// 1. Discovery relationship phase parses `dep`.
3357+
// 2. Discovery relationship phase parses `dependent`.
3358+
// 3. Runner pool task parses `dep`for apply.
3359+
// 4. Runner pool task parses `dependent` for apply.
3360+
// 5. While `dependent`'s HCL is being decoded, the `dependency.dep`
3361+
// block fetches `dep`'s outputs, which requires parsing `dep` again.
3362+
//
3363+
assert.Len(t, calls, 5)
3364+
3365+
for _, call := range calls {
3366+
assert.NotContains(t, call.WorkingDir, ".terragrunt-cache")
3367+
}
3368+
}
3369+
33103370
func TestIamRolesLoadingFromDifferentModules(t *testing.T) {
33113371
t.Parallel()
33123372

0 commit comments

Comments
 (0)