diff --git a/remote/remote_state.go b/remote/remote_state.go index 4f46d7b16..5b3baacf6 100644 --- a/remote/remote_state.go +++ b/remote/remote_state.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/gruntwork-io/go-commons/errors" + "github.com/gruntwork-io/terragrunt/cli/commands" "github.com/gruntwork-io/terragrunt/codegen" "github.com/gruntwork-io/terragrunt/internal/cache" "github.com/gruntwork-io/terragrunt/options" @@ -94,10 +95,16 @@ func (remoteState *RemoteState) Initialize(ctx context.Context, terragruntOption // Returns true if remote state needs to be configured. This will be the case when: // -// 1. Remote state has not already been configured -// 2. Remote state has been configured, but with a different configuration -// 3. The remote state initializer for this backend type, if there is one, says initialization is necessary +// 1. Remote state auto-initialization has been disabled +// 2. Remote state has not already been configured +// 3. Remote state has been configured, but with a different configuration +// 4. The remote state initializer for this backend type, if there is one, says initialization is necessary func (remoteState *RemoteState) NeedsInit(terragruntOptions *options.TerragruntOptions) (bool, error) { + if terragruntOptions.DisableBucketUpdate { + terragruntOptions.Logger.Debugf("Skipping remote state initialization due to %s flag", commands.TerragruntDisableBucketUpdateFlagName) + return false, nil + } + state, err := ParseTerraformStateFileFromLocation(remoteState.Backend, remoteState.Config, terragruntOptions.WorkingDir, terragruntOptions.DataDir()) if err != nil { return false, err diff --git a/test/fixture/main.tf b/test/fixture/main.tf index cb8a117d9..e18815073 100644 --- a/test/fixture/main.tf +++ b/test/fixture/main.tf @@ -1,12 +1,19 @@ terraform { backend "s3" {} + + required_providers { + external = { + source = "hashicorp/external" + version = "2.3.3" + } + } } -# Create an arbitrary local resource -data "template_file" "test" { - template = "Hello, I am a template. My sample_var value = $${sample_var}" +data "external" "hello" { + program = ["bash", "-c", "sample_var=\"$$(cat - jq '.sample_var')\" && echo '{\"output\": \"Hello, I am a template. My sample_var value = $$sample_var\"}'"] - vars = { + query = { sample_var = var.sample_var } -} \ No newline at end of file +} + diff --git a/test/fixture/outputs.tf b/test/fixture/outputs.tf index 03f42dafc..00c9afed0 100644 --- a/test/fixture/outputs.tf +++ b/test/fixture/outputs.tf @@ -1,3 +1,4 @@ output "rendered_template" { - value = data.template_file.test.rendered -} \ No newline at end of file + value = data.external.hello.result["output"] +} + diff --git a/test/integration_s3_encryption_test.go b/test/integration_s3_encryption_test.go index cf51363f2..1c4405c4f 100644 --- a/test/integration_s3_encryption_test.go +++ b/test/integration_s3_encryption_test.go @@ -154,6 +154,28 @@ func TestTerragruntS3EncryptionWarning(t *testing.T) { assert.NotContains(t, output, "Encryption is not enabled on the S3 remote state bucket "+s3BucketName) } +func TestTerragruntSkipBackend(t *testing.T) { + t.Parallel() + + tmpEnvPath := copyEnvironment(t, s3SSEAESFixturePath) + cleanupTerraformFolder(t, tmpEnvPath) + testPath := util.JoinPath(tmpEnvPath, s3SSEAESFixturePath) + + // The bucket and table name here are intentionally invalid. + tmpTerragruntConfigPath := createTmpTerragruntConfig(t, s3SSEAESFixturePath, "N/A", "N/A", config.DefaultTerragruntConfigPath) + + _, _, err := runTerragruntCommandWithOutput(t, "terragrunt init --terragrunt-non-interactive --terragrunt-config "+tmpTerragruntConfigPath+" --terragrunt-working-dir "+testPath+" -backend=false") + require.Error(t, err) + + lockFile := util.JoinPath(testPath, ".terraform.lock.hcl") + assert.False(t, util.FileExists(lockFile), "Lock file %s exists", lockFile) + + _, _, err = runTerragruntCommandWithOutput(t, "terragrunt init --terragrunt-non-interactive --terragrunt-config "+tmpTerragruntConfigPath+" --terragrunt-working-dir "+testPath+" --terragrunt-disable-bucket-update -backend=false") + require.NoError(t, err) + + assert.True(t, util.FileExists(lockFile), "Lock file %s does not exist", lockFile) +} + func applyCommand(configPath, fixturePath string) string { return fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-config %s --terragrunt-working-dir %s", configPath, fixturePath) } diff --git a/test/integration_test.go b/test/integration_test.go index efc147d1d..dfb969caf 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -4718,6 +4718,12 @@ func assertS3PublicAccessBlocks(t *testing.T, client *s3.S3, bucketName string) assert.True(t, aws.BoolValue(publicAccessBlockConfig.RestrictPublicBuckets)) } +// createS3Bucket creates a test S3 bucket for state. +func createS3Bucket(t *testing.T, awsRegion string, bucketName string) { + err := createS3BucketE(t, awsRegion, bucketName) + require.NoError(t, err) +} + // createS3BucketE create test S3 bucket. func createS3BucketE(t *testing.T, awsRegion string, bucketName string) error { mockOptions, err := options.NewTerragruntOptionsForTest("integration_test") @@ -4744,6 +4750,41 @@ func createS3BucketE(t *testing.T, awsRegion string, bucketName string) error { return nil } +// createDynamoDbTable creates a test DynamoDB table. +func createDynamoDbTable(t *testing.T, awsRegion string, tableName string) { + err := createDynamoDbTableE(t, awsRegion, tableName) + require.NoError(t, err) +} + +// createDynamoDbTableE creates a test DynamoDB table, and returns an error if the table creation fails. +func createDynamoDbTableE(t *testing.T, awsRegion string, tableName string) error { + client := createDynamoDbClientForTest(t, awsRegion) + _, err := client.CreateTable(&dynamodb.CreateTableInput{ + AttributeDefinitions: []*dynamodb.AttributeDefinition{ + { + AttributeName: aws.String("LockID"), + AttributeType: aws.String("S"), + }, + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("LockID"), + KeyType: aws.String("HASH"), + }, + }, + TableName: aws.String(tableName), + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(1), + WriteCapacityUnits: aws.Int64(1), + }, + }) + if err != nil { + return err + } + client.WaitUntilTableExists(&dynamodb.DescribeTableInput{TableName: aws.String(tableName)}) + return nil +} + // deleteS3BucketWithRetry will attempt to delete the specified S3 bucket, retrying up to 3 times if there are errors to // handle eventual consistency issues. func deleteS3BucketWithRetry(t *testing.T, awsRegion string, bucketName string) { @@ -6487,8 +6528,8 @@ func TestTerragruntDisableBucketUpdate(t *testing.T) { s3BucketName := "terragrunt-test-bucket-" + strings.ToLower(uniqueId()) lockTableName := "terragrunt-test-locks-" + strings.ToLower(uniqueId()) - err := createS3BucketE(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) - require.NoError(t, err) + createS3Bucket(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) + createDynamoDbTable(t, TERRAFORM_REMOTE_STATE_S3_REGION, lockTableName) defer deleteS3Bucket(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) defer cleanupTableForTest(t, lockTableName, TERRAFORM_REMOTE_STATE_S3_REGION) @@ -6497,7 +6538,7 @@ func TestTerragruntDisableBucketUpdate(t *testing.T) { runTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-disable-bucket-update --terragrunt-non-interactive --terragrunt-config %s --terragrunt-working-dir %s", tmpTerragruntConfigPath, rootPath)) - _, err = bucketPolicy(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) + _, err := bucketPolicy(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) // validate that bucket policy is not updated, because of --terragrunt-disable-bucket-update require.Error(t, err) } @@ -6937,8 +6978,7 @@ func TestTerragruntUpdatePolicy(t *testing.T) { s3BucketName := "terragrunt-test-bucket-" + strings.ToLower(uniqueId()) lockTableName := "terragrunt-test-locks-" + strings.ToLower(uniqueId()) - err := createS3BucketE(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) - require.NoError(t, err) + createS3Bucket(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) defer deleteS3Bucket(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) defer cleanupTableForTest(t, lockTableName, TERRAFORM_REMOTE_STATE_S3_REGION) @@ -6946,7 +6986,7 @@ func TestTerragruntUpdatePolicy(t *testing.T) { tmpTerragruntConfigPath := createTmpTerragruntConfig(t, rootPath, s3BucketName, lockTableName, config.DefaultTerragruntConfigPath) // check that there is no policy on created bucket - _, err = bucketPolicy(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) + _, err := bucketPolicy(t, TERRAFORM_REMOTE_STATE_S3_REGION, s3BucketName) require.Error(t, err) runTerragrunt(t, fmt.Sprintf("terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-config %s --terragrunt-working-dir %s", tmpTerragruntConfigPath, rootPath))