Repository: gruntwork-io/terragrunt
Pull request #4089: fix: Fixing `backend delete` safety features
Author: login: yhakbar
Created: 2025-03-27T13:36:36Z | Merged: 2025-03-27T22:56:53Z

<!-- Prepend '[WIP]' to the title if this PR is still a work-in-progress. Remove it when it is ready for review! -->

## Description

Adds requisite check that the backend state bucket is versioned before performing any state deletion as a safeguard for users.

<!-- Description of the changes introduced by this PR. -->

## TODOs

Read the [Gruntwork contribution guidelines](https://gruntwork.notion.site/Gruntwork-Coding-Methodology-02fdcd6e4b004e818553684760bf691e).

- [x] Update the docs.
- [x] Run the relevant tests successfully, including pre-commit checks.
- [x] Ensure any 3rd party code adheres with our [license policy](https://www.notion.so/gruntwork/Gruntwork-licenses-and-open-source-usage-policy-f7dece1f780341c7b69c1763f22b1378) or delete this line if its not applicable.
- [x] Include release notes. If this PR is backward incompatible, include a migration guide.

## Release Notes (draft)

<!-- One-line description of the PR that can be included in the final release notes. -->
Added `--force` flag to bypass safety feature disallowing usage of `backend delete` on buckets that are not versioned.

### Migration Guide

<!-- Important: If you made any backward incompatible changes, then you must write a migration guide! -->



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added a new CLI option, `--force`, for backend deletion. When used, it allows the deletion to proceed even if bucket versioning isn’t enabled.
  - Introduced a configuration option to conditionally skip bucket versioning during remote state bootstrapping.
  
- **Tests**
  - Expanded integration tests for AWS and GCP backends to cover scenarios with versioning disabled and to verify the behavior of the new force deletion capability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/cli/commands/backend/delete/cli.go b/cli/commands/backend/delete/cli.go
index b2c5adf946..cc2ae97149 100644
--- a/cli/commands/backend/delete/cli.go
+++ b/cli/commands/backend/delete/cli.go
@@ -11,7 +11,8 @@ import (
 const (
 	CommandName = "delete"
 
-	BucketFlagName = "bucket"
+	BucketFlagName             = "bucket"
+	ForceBackendDeleteFlagName = "force"
 )
 
 func NewFlags(cmdOpts *Options, prefix flags.Prefix) cli.Flags {
@@ -25,6 +26,12 @@ func NewFlags(cmdOpts *Options, prefix flags.Prefix) cli.Flags {
 			Hidden:      true,
 			Destination: &cmdOpts.DeleteBucket,
 		}),
+		flags.NewFlag(&cli.BoolFlag{
+			Name:        ForceBackendDeleteFlagName,
+			EnvVars:     tgPrefix.EnvVars(ForceBackendDeleteFlagName),
+			Usage:       "Force the backend to be deleted, even if the bucket is not versioned.",
+			Destination: &cmdOpts.ForceBackendDelete,
+		}),
 	}
 
 	return append(flags, run.NewFlags(cmdOpts.TerragruntOptions, nil).Filter(run.ConfigFlagName, run.DownloadDirFlagName)...)
diff --git a/internal/remotestate/backend/gcs/backend.go b/internal/remotestate/backend/gcs/backend.go
index dddfd8041b..5e3217343f 100644
--- a/internal/remotestate/backend/gcs/backend.go
+++ b/internal/remotestate/backend/gcs/backend.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/gruntwork-io/terragrunt/internal/errors"
 	"github.com/gruntwork-io/terragrunt/internal/remotestate/backend"
 	"github.com/gruntwork-io/terragrunt/options"
 	"github.com/gruntwork-io/terragrunt/shell"
@@ -108,7 +109,7 @@ func (backend *Backend) Init(ctx context.Context, backendConfig backend.Config,
 	// If bucket is specified and skip_bucket_versioning is false then warn user if versioning is disabled on bucket
 	if !extGCSCfg.SkipBucketVersioning && bucketName != "" {
 		// TODO: Remove lint suppression
-		if err := client.CheckIfGCSVersioningEnabled(bucketName); err != nil { //nolint:contextcheck
+		if _, err := client.CheckIfGCSVersioningEnabled(ctx, bucketName); err != nil { //nolint:contextcheck
 			return err
 		}
 	}
@@ -130,6 +131,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
+		versioned, err := client.CheckIfGCSVersioningEnabled(ctx, extGCSCfg.RemoteStateConfigGCS.Bucket)
+		if err != nil {
+			return err
+		}
+
+		if !versioned {
+			return errors.New("bucket is not versioned, refusing to delete backend state. If you are sure you want to delete the backend state anyways, use the --force flag")
+		}
+	}
+
 	var (
 		bucketName = extGCSCfg.RemoteStateConfigGCS.Bucket
 		prefix     = extGCSCfg.RemoteStateConfigGCS.Prefix
diff --git a/internal/remotestate/backend/gcs/client.go b/internal/remotestate/backend/gcs/client.go
index 8d16754c8e..347d494832 100644
--- a/internal/remotestate/backend/gcs/client.go
+++ b/internal/remotestate/backend/gcs/client.go
@@ -156,21 +156,20 @@ func (client *Client) CreateGCSBucketIfNecessary(ctx context.Context, bucketName
 }
 
 // CheckIfGCSVersioningEnabled checks if versioning is enabled for the GCS bucket specified in the given config and warn the user if it is not
-func (client *Client) CheckIfGCSVersioningEnabled(bucketName string) error {
-	ctx := context.Background()
+func (client *Client) CheckIfGCSVersioningEnabled(ctx context.Context, bucketName string) (bool, error) {
 	bucket := client.Bucket(bucketName)
 
 	attrs, err := bucket.Attrs(ctx)
 	if err != nil {
 		// ErrBucketNotExist
-		return errors.New(err)
+		return false, errors.New(err)
 	}
 
 	if !attrs.VersioningEnabled {
-		client.logger.Warnf("Versioning is not enabled for the remote state GCS bucket %s. We recommend enabling versioning so that you can roll back to previous versions of your Terraform state in case of error.", bucketName)
+		client.logger.Warnf("Versioning is not enabled for the remote state GCS bucket %s. We recommend enabling versioning so that you can roll back to previous versions of your OpenTofu/Terraform state in case of error.", bucketName)
 	}
 
-	return nil
+	return attrs.VersioningEnabled, nil
 }
 
 // CreateGCSBucketWithVersioning creates the given GCS bucket and enables versioning for it.
diff --git a/internal/remotestate/backend/s3/backend.go b/internal/remotestate/backend/s3/backend.go
index ca9725fe70..907c0bb802 100644
--- a/internal/remotestate/backend/s3/backend.go
+++ b/internal/remotestate/backend/s3/backend.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"path"
 
+	"github.com/gruntwork-io/terragrunt/internal/errors"
 	"github.com/gruntwork-io/terragrunt/internal/remotestate/backend"
 	"github.com/gruntwork-io/terragrunt/options"
 	"github.com/gruntwork-io/terragrunt/shell"
@@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
+		versioned, err := client.CheckIfVersioningEnabled(ctx, extS3Cfg.RemoteStateConfigS3.Bucket)
+		if err != nil {
+			return err
+		}
+
+		if !versioned {
+			return errors.New("bucket is not versioned, refusing to delete backend state. If you are sure you want to delete the backend state anyways, use the --force flag")
+		}
+	}
+
 	var (
 		bucketName = extS3Cfg.RemoteStateConfigS3.Bucket
 		bucketKey  = extS3Cfg.RemoteStateConfigS3.Key
diff --git a/internal/remotestate/backend/s3/client.go b/internal/remotestate/backend/s3/client.go
index e02b21c9b4..18e3e62136 100644
--- a/internal/remotestate/backend/s3/client.go
+++ b/internal/remotestate/backend/s3/client.go
@@ -416,7 +416,7 @@ func (client *Client) CheckIfVersioningEnabled(ctx context.Context, bucketName s
 	// NOTE: There must be a bug in the AWS SDK since res == nil when versioning is not enabled. In the future,
 	// check the AWS SDK for updates to see if we can remove "res == nil ||".
 	if res == nil || res.Status == nil || *res.Status != s3.BucketVersioningStatusEnabled {
-		client.logger.Warnf("Versioning is not enabled for the remote state S3 bucket %s. We recommend enabling versioning so that you can roll back to previous versions of your Terraform state in case of error.", bucketName)
+		client.logger.Warnf("Versioning is not enabled for the remote state S3 bucket %s. We recommend enabling versioning so that you can roll back to previous versions of your OpenTofu/Terraform state in case of error.", bucketName)
 		return false, nil
 	}
 
diff --git a/options/options.go b/options/options.go
index d53660ea40..8238aeb557 100644
--- a/options/options.go
+++ b/options/options.go
@@ -284,6 +284,8 @@ type TerragruntOptions struct {
 	Graph bool
 	// BackendBootstrap automatically bootstraps backend infrastructure before attempting to use it.
 	BackendBootstrap bool
+	// ForceBackendDelete forces the backend to be deleted, even if the bucket is not versioned.
+	ForceBackendDelete bool
 }
 
 // TerragruntOptionsFunc is a functional option type used to pass options in certain integration tests
diff --git a/test/fixtures/bootstrap-gcs-backend/common.hcl b/test/fixtures/bootstrap-gcs-backend/common.hcl
index 3f0130c150..e3f2b39516 100644
--- a/test/fixtures/bootstrap-gcs-backend/common.hcl
+++ b/test/fixtures/bootstrap-gcs-backend/common.hcl
@@ -1,3 +1,7 @@
+feature "disable_versioning" {
+  default = false
+}
+
 remote_state {
   backend = "gcs"
 
@@ -11,5 +15,7 @@ remote_state {
     location = "__FILL_IN_LOCATION__"
     project  = "__FILL_IN_PROJECT__"
     bucket   = "__FILL_IN_BUCKET_NAME__"
+
+    skip_bucket_versioning = feature.disable_versioning.value
   }
 }
diff --git a/test/fixtures/bootstrap-s3-backend/common.hcl b/test/fixtures/bootstrap-s3-backend/common.hcl
index 8242683124..0831ff0d4a 100644
--- a/test/fixtures/bootstrap-s3-backend/common.hcl
+++ b/test/fixtures/bootstrap-s3-backend/common.hcl
@@ -1,3 +1,7 @@
+feature "disable_versioning" {
+  default = false
+}
+
 remote_state {
   backend = "s3"
   generate = {
@@ -9,5 +13,7 @@ remote_state {
     bucket         = "__FILL_IN_BUCKET_NAME__"
     region         = "__FILL_IN_REGION__"
     dynamodb_table = "__FILL_IN_LOCK_TABLE_NAME__"
+
+    skip_bucket_versioning = feature.disable_versioning.value
   }
 }
diff --git a/test/integration_aws_test.go b/test/integration_aws_test.go
index e3f052ec5e..7dc7f22499 100644
--- a/test/integration_aws_test.go
+++ b/test/integration_aws_test.go
@@ -125,6 +125,39 @@ func TestAwsBootstrapBackend(t *testing.T) {
 	}
 }
 
+func TestAwsBootstrapBackendWithoutVersioning(t *testing.T) {
+	t.Parallel()
+
+	helpers.CleanupTerraformFolder(t, testFixtureBootstrapS3Backend)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureBootstrapS3Backend)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureBootstrapS3Backend)
+
+	testID := strings.ToLower(helpers.UniqueID())
+
+	s3BucketName := "terragrunt-test-bucket-" + testID
+	dynamoDBName := "terragrunt-test-dynamodb-" + testID
+
+	defer func() {
+		deleteS3Bucket(t, s3BucketName, helpers.TerraformRemoteStateS3Region)
+		cleanupTableForTest(t, dynamoDBName, helpers.TerraformRemoteStateS3Region)
+	}()
+
+	commonConfigPath := util.JoinPath(rootPath, "common.hcl")
+	helpers.CopyTerragruntConfigAndFillPlaceholders(t, commonConfigPath, commonConfigPath, s3BucketName, dynamoDBName, helpers.TerraformRemoteStateS3Region)
+
+	_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true --backend-bootstrap apply")
+	require.NoError(t, err)
+
+	validateS3BucketExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, s3BucketName, nil)
+	validateDynamoDBTableExistsAndIsTagged(t, helpers.TerraformRemoteStateS3Region, dynamoDBName, nil)
+
+	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true backend delete --all")
+	require.Error(t, err)
+
+	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true backend delete --all --force")
+	require.NoError(t, err)
+}
+
 func TestAwsDeleteBackend(t *testing.T) {
 	t.Parallel()
 
diff --git a/test/integration_gcp_test.go b/test/integration_gcp_test.go
index 51c727528c..32a7358fb6 100644
--- a/test/integration_gcp_test.go
+++ b/test/integration_gcp_test.go
@@ -96,6 +96,35 @@ func TestGcpBootstrapBackend(t *testing.T) {
 	}
 }
 
+func TestGcpBootstrapBackendWithoutVersioning(t *testing.T) {
+	t.Parallel()
+
+	helpers.CleanupTerraformFolder(t, testFixtureBootstrapGCSBackend)
+	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureBootstrapGCSBackend)
+	rootPath := util.JoinPath(tmpEnvPath, testFixtureBootstrapGCSBackend)
+
+	gcsBucketName := "terragrunt-test-bucket-" + strings.ToLower(helpers.UniqueID())
+
+	defer func() {
+		deleteGCSBucket(t, gcsBucketName)
+	}()
+
+	project := os.Getenv("GOOGLE_CLOUD_PROJECT")
+	commonConfigPath := util.JoinPath(rootPath, "common.hcl")
+	copyTerragruntGCSConfigAndFillPlaceholders(t, commonConfigPath, commonConfigPath, project, terraformRemoteStateGcpRegion, gcsBucketName)
+
+	_, _, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true --backend-bootstrap apply")
+	require.NoError(t, err)
+
+	validateGCSBucketExistsAndIsLabeled(t, terraformRemoteStateGcpRegion, gcsBucketName, nil)
+
+	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true backend delete --all")
+	require.Error(t, err)
+
+	_, _, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt --non-interactive --log-level debug --strict-control require-explicit-bootstrap --experiment cli-redesign --working-dir "+rootPath+" --feature disable_versioning=true backend delete --all --force")
+	require.NoError(t, err)
+}
+
 func TestGcpDeleteBackend(t *testing.T) {
 	t.Parallel()
 


Reviews:
user: login: lev-ok
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:42:57Z
user: login: yhakbar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T14:49:11Z
user: login: lev-ok
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:54:30Z
user: login: lev-ok
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:56:18Z
user: login: yhakbar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T14:58:42Z
user: login: yhakbar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T14:59:38Z
user: login: lev-ok
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T15:00:33Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>internal/remotestate/backend/gcs/backend.go (2)</summary><blockquote>

`112-112`: **Added context parameter to CheckIfGCSVersioningEnabled function.**

This update properly passes the context to the GCS client function, which improves context propagation throughout the application.



Consider addressing the TODO comment about removing the lint suppression if possible. If the suppression is still necessary, it might be helpful to include a brief explanation of why.

---

`134-143`: **Good safety enhancement for backend state deletion.**

This code adds an important safety check to prevent accidental deletion of state from non-versioned buckets. The implementation correctly:

1. Checks for the force flag before proceeding
2. Verifies if bucket versioning is enabled
3. Returns a clear error message if attempting to delete state from a non-versioned bucket without force flag



Consider enhancing the error message to briefly explain why versioning is important for state safety. For example:

```diff
-return errors.New("bucket is not versioned, refusing to delete backend state. If you are sure you want to delete the backend state anyways, use the --force flag")
+return errors.New("bucket is not versioned, refusing to delete backend state. Versioning enables recovery of accidentally deleted state. If you are sure you want to delete the backend state anyways, use the --force flag")
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d3be9290e537ec36b7fa2503a889c665c3a1c69d and 0f7322bcb7c847e2886288938a1fb9a945592b8c.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `cli/commands/backend/delete/cli.go` (2 hunks)
* `internal/remotestate/backend/gcs/backend.go` (3 hunks)
* `internal/remotestate/backend/gcs/client.go` (1 hunks)
* `internal/remotestate/backend/s3/backend.go` (2 hunks)
* `internal/remotestate/backend/s3/client.go` (1 hunks)
* `options/options.go` (1 hunks)
* `test/fixtures/bootstrap-gcs-backend/common.hcl` (2 hunks)
* `test/fixtures/bootstrap-s3-backend/common.hcl` (2 hunks)
* `test/integration_aws_test.go` (1 hunks)
* `test/integration_gcp_test.go` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.go`: Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.</summary>

> `**/*.go`: Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

- `internal/remotestate/backend/s3/backend.go`
- `cli/commands/backend/delete/cli.go`
- `options/options.go`
- `internal/remotestate/backend/gcs/client.go`
- `test/integration_aws_test.go`
- `internal/remotestate/backend/s3/client.go`
- `internal/remotestate/backend/gcs/backend.go`
- `test/integration_gcp_test.go`

</details>

</details><details>
<summary>🧬 Code Definitions (2)</summary>

<details>
<summary>test/integration_aws_test.go (3)</summary><blockquote>

<details>
<summary>test/helpers/package.go (5)</summary>

* `CleanupTerraformFolder` (719-726)
* `CopyEnvironment` (83-96)
* `UniqueID` (153-166)
* `CopyTerragruntConfigAndFillPlaceholders` (124-133)
* `RunTerragruntCommandWithOutput` (827-831)

</details>
<details>
<summary>util/file.go (1)</summary>

* `JoinPath` (472-474)

</details>
<details>
<summary>pkg/log/log.go (1)</summary>

* `Error` (42-44)

</details>

</blockquote></details>
<details>
<summary>test/integration_gcp_test.go (3)</summary><blockquote>

<details>
<summary>test/helpers/package.go (4)</summary>

* `CleanupTerraformFolder` (719-726)
* `CopyEnvironment` (83-96)
* `UniqueID` (153-166)
* `RunTerragruntCommandWithOutput` (827-831)

</details>
<details>
<summary>util/file.go (1)</summary>

* `JoinPath` (472-474)

</details>
<details>
<summary>pkg/log/log.go (1)</summary>

* `Error` (42-44)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Pull Request has non-contributor approval

</details>

<details>
<summary>🔇 Additional comments (15)</summary><blockquote>

<details>
<summary>test/fixtures/bootstrap-s3-backend/common.hcl (2)</summary>

`1-3`: **Good addition of a versioning control feature flag.**

Adding the `disable_versioning` feature with a default of `false` is a sensible approach to allow optional control over bucket versioning while defaulting to the safer option (versioning enabled).

---

`17-17`: **Well-implemented usage of the feature flag.**

The feature flag is appropriately used to control the `skip_bucket_versioning` setting, maintaining backward compatibility and ensuring that versioning remains enabled by default.

</details>
<details>
<summary>options/options.go (1)</summary>

`287-288`: **Good safety enhancement with clear documentation.**

Adding the `ForceBackendDelete` option improves safety by requiring explicit confirmation before deleting buckets without versioning. The comment clearly explains the purpose of this field.

</details>
<details>
<summary>test/fixtures/bootstrap-gcs-backend/common.hcl (2)</summary>

`1-3`: **Consistent implementation of versioning control across backends.**

This implementation of the `disable_versioning` feature for GCS matches the S3 implementation, maintaining consistency across different backend types.

---

`19-19`: **Appropriate use of the versioning feature flag.**

The feature flag is correctly applied to the GCS backend configuration in the same way as it is for the S3 backend, ensuring consistent behavior across different backend types.

</details>
<details>
<summary>internal/remotestate/backend/s3/client.go (1)</summary>

`419-419`: **Updated message to include OpenTofu references.**

The warning message has been updated to mention "OpenTofu/Terraform" instead of just "Terraform", correctly reflecting that Terragrunt now supports both implementations.

</details>
<details>
<summary>internal/remotestate/backend/s3/backend.go (1)</summary>

`142-151`: **Enhance safety with versioning check on bucket deletion.**

The new code adds a critical safety mechanism by requiring versioning to be enabled on S3 buckets before allowing backend state deletion, unless explicitly forced. This prevents accidental deletion of state from non-versioned buckets, where recovery would be impossible.


The implementation correctly:
1. Checks `opts.ForceBackendDelete` flag
2. Verifies versioning status using `CheckIfVersioningEnabled`
3. Returns a clear error message instructing users how to proceed if needed

This aligns well with the PR objective to improve backend delete safety features.

</details>
<details>
<summary>cli/commands/backend/delete/cli.go (2)</summary>

`15-15`: **Add force flag constant for consistent usage.**

The new constant `ForceBackendDeleteFlagName` provides a single source of truth for the flag name, improving maintainability.

---

`29-34`: **Implement force flag for backend deletion operations.**

The implementation correctly adds the `--force` flag that allows users to override the versioning safety check when deleting backend state.


The flag definition is well-structured with:
- Proper name and environment variable support
- Clear usage description explaining the flag's purpose
- Direct binding to the `ForceBackendDelete` field in options

</details>
<details>
<summary>test/integration_gcp_test.go (1)</summary>

`99-126`: **Comprehensive test validating versioning safety mechanism.**

This test thoroughly validates the new force flag behavior with GCP backends when versioning is disabled. It tests both the expected failure case (without --force) and success case (with --force).


The test properly:
1. Creates a test bucket with versioning disabled
2. Verifies the bucket exists with correct labeling
3. Confirms deletion fails without the force flag
4. Confirms deletion succeeds with the force flag

This test provides good coverage for the new safety feature and follows the existing test patterns in the codebase.

</details>
<details>
<summary>internal/remotestate/backend/gcs/client.go (4)</summary>

`159-159`: **Improved function signature returns versioning status.**

The updated function signature now returns a boolean indicating whether versioning is enabled, along with an error if applicable. This change allows callers to make decisions based on the versioning status.


This is a good improvement that makes the function more useful and consistent with similar checks in the codebase.

---

`165-165`: **Properly return versioning status with error.**

When an error occurs accessing bucket attributes, the function now correctly returns `false` along with the error, maintaining a consistent return pattern.

---

`169-169`: **Updated warning message to include OpenTofu.**

The warning message has been updated to reference "OpenTofu/Terraform state" rather than just "Terraform state," reflecting the project's support for both tools.

---

`172-172`: **Return actual versioning status to caller.**

The function now returns the actual value of `attrs.VersioningEnabled`, providing the caller with the true versioning status.


This change completes the function's updated signature and ensures the return value reflects the actual bucket configuration.

</details>
<details>
<summary>test/integration_aws_test.go (1)</summary>

`128-159`: **Well-structured test for the new backend delete safety feature.**

This test properly verifies that non-versioned S3 buckets cannot be deleted without the `--force` flag. The test follows best practices by:

1. Running in parallel with other tests
2. Using unique resource names
3. Properly handling resource cleanup
4. Testing both failure and success cases

The test accurately validates the new safety behavior by ensuring that:
- A bucket created with versioning disabled can be bootstrapped successfully
- Attempting to delete the backend without `--force` fails (lines 154-155)
- Using the `--force` flag allows deletion to proceed (lines 157-158)

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T18:17:47Z
user: login: lev-ok
body: Great job 👍
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T22:56:49Z

Review comments:
diff_hunk: @@ -105,7 +105,15 @@ func (cmd *Command) Subcommand(name string) *Command {
 
 // VisibleFlags returns a slice of the Flags, used by `urfave/cli` package to generate help.
 func (cmd *Command) VisibleFlags() Flags {
-	return cmd.Flags
+	visibleFlags := make(Flags, 0, len(cmd.Flags))
+
+	for _, flag := range cmd.Flags {
+		if !flag.GetHidden() {
+			visibleFlags = append(visibleFlags, flag)
+		}
+	}
+
+	return visibleFlags
path: internal/cli/command.go
user: login: lev-ok
body: ```suggestion
	return cmd.Flags.VisibleFlags()
```
created_at: 2025-03-27T14:38:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: NIT: I'd rather pass it as an argument to the function.
created_at: 2025-03-27T14:42:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: yhakbar
body: We shouldn't have conflicting sources of truth. If we're going to take it as an argument, then we shouldn't have `opts` as an argument, as that will have another copy of the same value.
created_at: 2025-03-27T14:49:10Z
author_association: COLLABORATOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: It would be great if `remotestate` didn't know anything about `options.TerragruntOptions` at all, as stated in the book `Clean Architecture`, the called method shouldn't know anything about the properties etc. of the called method.
created_at: 2025-03-27T14:54:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: I tried to implement this during the refactoring, but because we pass some authorization parameters via options, I didn't spend much time on it.
created_at: 2025-03-27T14:56:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: So, the suggestion is

```
Delete(ctx context.Context, forceBackendDelete bool, opts *options.TerragruntOptions) error
```
created_at: 2025-03-27T14:58:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: yhakbar
body: I totally agree, but as it stands, it does. While it has access to the `opts` variable, it shouldn't receive potentially conflicting data from a different variable. 

If we want to make it agnostic, we need to refactor out all usage of `opts`, including in `NewClient`, etc. so that there isn't risk of conflicting information. 

e.g. What if I set the argument `forceBackendDelete` to true, but `opts.ForceBackendDelete` is false? It's an inconsistency that can lead to bugs.
created_at: 2025-03-27T14:58:42Z
author_association: COLLABORATOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: delete/delete.go

```go
func Run(ctx context.Context, cmdOpts *Options) error {
...

	return remoteState.Delete(ctx, opts.ForceBackendDelete, opts)
}
```
created_at: 2025-03-27T14:59:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: yhakbar
body: It's better to avoid that kind of refactor without making it all we do in the PR. We use `opts` everywhere, so we need to be careful with how we remove usage.
created_at: 2025-03-27T14:59:38Z
author_association: COLLABORATOR
diff_hunk: @@ -138,6 +139,17 @@ func (backend *Backend) Delete(ctx context.Context, backendConfig backend.Config
 		return err
 	}
 
+	if !opts.ForceBackendDelete {
path: internal/remotestate/backend/s3/backend.go
user: login: lev-ok
body: OK, do what you think is right, I just advised.
created_at: 2025-03-27T15:00:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-03-27T13:36:40Z
body: [vc]: #HCcCbXFIr40ccRzFO1NatV9vWXUTdaqeI0KJoMJRvl4=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0ZXJyYWdydW50LWRvY3MiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoidGVycmFncnVudC1kb2NzLWdpdC1maXgtZml4aW5nLWJhY2tlbmQtc2FmZXR5LTU0MjI2MC1ncnVudHdvcmsudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vZ3J1bnR3b3JrL3RlcnJhZ3J1bnQtZG9jcy8ycWNyZlVUcDVvRGdFZ2QxYmZQRTZMWlhmQk5qIiwicHJldmlld1VybCI6InRlcnJhZ3J1bnQtZG9jcy1naXQtZml4LWZpeGluZy1iYWNrZW5kLXNhZmV0eS01NDIyNjAtZ3J1bnR3b3JrLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjoiZG9jcy1zdGFybGlnaHQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **terragrunt-docs** | ✅ Ready ([Inspect](https://vercel.com/gruntwork/terragrunt-docs/2qcrfUTp5oDgEgd1bfPE6LZXfBNj)) | [Visit Preview](https://terragrunt-docs-git-fix-fixing-backend-safety-542260-gruntwork.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/terragrunt-docs-git-fix-fixing-backend-safety-542260-gruntwork.vercel.app?via=pr-comment-feedback-link) | Mar 27, 2025 4:40pm |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-27T13:36:44Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This pull request introduces a new command-line flag (`--force`) for backend deletion. It updates the deletion logic in both GCS and S3 backends to check for bucket versioning and only allow deletion when versioning is enabled or the force flag is provided. The GCS client function now accepts a context and returns the versioning status explicitly. New configuration options in bootstrap HCL files support the conditional control of versioning, and additional integration tests for AWS and GCP validate the behavior when versioning is disabled.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `cli/commands/backend/delete/cli.go` | Added `ForceBackendDeleteFlagName = "force"` constant and updated flag initialization in `NewFlags` to support a new boolean option. |
| `internal/remotestate/backend/gcs/backend.go`<br>`internal/remotestate/backend/gcs/client.go` | Updated GCS backend: In `backend.go`, modified `Init` and `Delete` methods to pass context and check the force flag; in `client.go`, changed `CheckIfGCSVersioningEnabled` to accept a context and return `(bool, error)` with updated warning messages. |
| `internal/remotestate/backend/s3/backend.go`<br>`internal/remotestate/backend/s3/client.go` | Enhanced S3 backend: In `backend.go`, added deletion error handling based on bucket versioning and force flag; in `client.go`, updated the warning message to reference "OpenTofu/Terraform state". |
| `options/options.go` | Added a new boolean field `ForceBackendDelete` to the `TerragruntOptions` struct. |
| `test/fixtures/bootstrap-gcs-backend/common.hcl`<br>`test/fixtures/bootstrap-s3-backend/common.hcl` | Added a new feature block `disable_versioning` (default false) and incorporated it into the `remote_state` configuration via the `skip_bucket_versioning` option. |
| `test/integration_aws_test.go`<br>`test/integration_gcp_test.go` | Introduced new test functions to validate AWS and GCP bootstrap backends when versioning is disabled, including error handling when deletion is attempted without the force flag. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant CLI
    participant Options
    participant Backend
    participant Client

    User->>CLI: Execute delete command (--force flag optional)
    CLI->>Options: Parse flags (including ForceBackendDelete)
    Options->>Backend: Call Delete(ctx, config, options)
    alt Force flag is set
        Backend->>Client: (Skip versioning check)
        Client-->>Backend: N/A
        Backend-->>CLI: Proceed with deletion
    else Force flag not set
        Backend->>Client: Call CheckIfVersioningEnabled(ctx, bucketName)
        Client-->>Backend: Return (versioned status, error)
        alt Bucket is versioned
            Backend-->>CLI: Proceed with deletion
        else Bucket is not versioned
            Backend-->>CLI: Return error ("backend cannot be deleted without force flag")
        end
    end
```

## Suggested Reviewers

- denis256
- levkohimins

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=gruntwork-io/terragrunt&utm_content=4089):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai plan` to trigger planning for file edits and PR creation.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR