Skip to content

Commit ccb72ec

Browse files
Add deletion_policy field to Project resource (#11359) (#19026)
[upstream:4bb92c3b9037ad597bdc7b0c163cb720e896a003] Signed-off-by: Modular Magician <magic-modules@google.com>
1 parent 03a13b4 commit ccb72ec

File tree

5 files changed

+133
-17
lines changed

5 files changed

+133
-17
lines changed

.changelog/11359.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
resourcemanager: added `deletion_policy` field to replace `skip_delete` field in `google_project` resource. Setting `deletion_policy` to `PREVENT` will protect the project against any destroy actions caused by a terraform apply or terraform destroy. Setting `deletion_policy` to `ABANDON` allows the resource to be abandoned rather than deleted and it behaves the same with `skip_delete = true`. Default value is `DELETE`.
3+
```

google/services/resourcemanager/data_source_google_project_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestAccDataSourceGoogleProject_basic(t *testing.T) {
3030
// Virtual fields
3131
"auto_create_network": {},
3232
"skip_delete": {},
33+
"deletion_policy": {},
3334
}),
3435
),
3536
},

google/services/resourcemanager/resource_google_project.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/hashicorp/errwrap"
1616
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1717
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
18+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1819

1920
tpgcompute "github.com/hashicorp/terraform-provider-google/google/services/compute"
2021
tpgserviceusage "github.com/hashicorp/terraform-provider-google/google/services/serviceusage"
@@ -75,6 +76,14 @@ func ResourceGoogleProject() *schema.Resource {
7576
Computed: true,
7677
Description: `If true, the Terraform resource can be deleted without deleting the Project via the Google API.`,
7778
},
79+
"deletion_policy": {
80+
Type: schema.TypeString,
81+
Optional: true,
82+
Default: "DELETE",
83+
Description: `The deletion policy for the Project. Setting PREVENT will protect the project against any destroy actions caused by a terraform apply or terraform destroy. Setting ABANDON allows the resource
84+
to be abandoned rather than deleted. Possible values are: "PREVENT", "ABANDON", "DELETE"`,
85+
ValidateFunc: validation.StringInSlice([]string{"PREVENT", "ABANDON", "DELETE"}, false),
86+
},
7887
"auto_create_network": {
7988
Type: schema.TypeBool,
8089
Optional: true,
@@ -307,7 +316,12 @@ func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
307316
d.SetId("")
308317
return nil
309318
}
310-
319+
// Explicitly set client-side fields to default values if unset
320+
if _, ok := d.GetOkExists("deletion_policy"); !ok {
321+
if err := d.Set("deletion_policy", "DELETE"); err != nil {
322+
return fmt.Errorf("Error setting deletion_policy: %s", err)
323+
}
324+
}
311325
if err := d.Set("project_id", pid); err != nil {
312326
return fmt.Errorf("Error setting project_id: %s", err)
313327
}
@@ -501,18 +515,29 @@ func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error
501515
if err != nil {
502516
return err
503517
}
518+
deletionPolicy := d.Get("deletion_policy").(string)
504519
// Only delete projects if skip_delete isn't set
505-
if !d.Get("skip_delete").(bool) {
506-
parts := strings.Split(d.Id(), "/")
507-
pid := parts[len(parts)-1]
508-
if err := transport_tpg.Retry(transport_tpg.RetryOptions{
509-
RetryFunc: func() error {
510-
_, delErr := config.NewResourceManagerClient(userAgent).Projects.Delete(pid).Do()
511-
return delErr
512-
},
513-
Timeout: d.Timeout(schema.TimeoutDelete),
514-
}); err != nil {
515-
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Project %s", pid))
520+
if deletionPolicy == "PREVENT" {
521+
return fmt.Errorf("Cannot destroy project as deletion_policy is set to PREVENT.")
522+
} else if deletionPolicy == "ABANDON" {
523+
log.Printf("[WARN] The project has been abandoned as deletion_policy set to ABANDON.")
524+
d.SetId("")
525+
return nil
526+
} else {
527+
// Only delete projects if deletion_policy isn't PREVENT or ABANDON
528+
// Only delete projects if skip_delete isn't set
529+
if !d.Get("skip_delete").(bool) {
530+
parts := strings.Split(d.Id(), "/")
531+
pid := parts[len(parts)-1]
532+
if err := transport_tpg.Retry(transport_tpg.RetryOptions{
533+
RetryFunc: func() error {
534+
_, delErr := config.NewResourceManagerClient(userAgent).Projects.Delete(pid).Do()
535+
return delErr
536+
},
537+
Timeout: d.Timeout(schema.TimeoutDelete),
538+
}); err != nil {
539+
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("Project %s", pid))
540+
}
516541
}
517542
}
518543
d.SetId("")

google/services/resourcemanager/resource_google_project_test.go

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"reflect"
9+
"regexp"
910
"strconv"
1011
"strings"
1112
"testing"
@@ -97,7 +98,7 @@ func TestAccProject_billing(t *testing.T) {
9798
ResourceName: "google_project.acceptance",
9899
ImportState: true,
99100
ImportStateVerify: true,
100-
ImportStateVerifyIgnore: []string{"skip_delete"},
101+
ImportStateVerifyIgnore: []string{"skip_delete", "deletion_policy"},
101102
},
102103
// Update to a different billing account
103104
{
@@ -138,7 +139,7 @@ func TestAccProject_labels(t *testing.T) {
138139
ResourceName: "google_project.acceptance",
139140
ImportState: true,
140141
ImportStateVerify: true,
141-
ImportStateVerifyIgnore: []string{"skip_delete", "labels", "terraform_labels"},
142+
ImportStateVerifyIgnore: []string{"skip_delete", "labels", "terraform_labels", "deletion_policy"},
142143
},
143144
// update project with labels
144145
{
@@ -211,7 +212,7 @@ func TestAccProject_migrateParent(t *testing.T) {
211212
ResourceName: "google_project.acceptance",
212213
ImportState: true,
213214
ImportStateVerify: true,
214-
ImportStateVerifyIgnore: []string{"skip_delete"},
215+
ImportStateVerifyIgnore: []string{"skip_delete", "deletion_policy"},
215216
},
216217
{
217218
Config: testAccProject_migrateParentOrg(pid, folderDisplayName, org),
@@ -220,7 +221,7 @@ func TestAccProject_migrateParent(t *testing.T) {
220221
ResourceName: "google_project.acceptance",
221222
ImportState: true,
222223
ImportStateVerify: true,
223-
ImportStateVerifyIgnore: []string{"skip_delete"},
224+
ImportStateVerifyIgnore: []string{"skip_delete", "deletion_policy"},
224225
},
225226
{
226227
Config: testAccProject_migrateParentFolder(pid, folderDisplayName, org),
@@ -229,7 +230,7 @@ func TestAccProject_migrateParent(t *testing.T) {
229230
ResourceName: "google_project.acceptance",
230231
ImportState: true,
231232
ImportStateVerify: true,
232-
ImportStateVerifyIgnore: []string{"skip_delete"},
233+
ImportStateVerifyIgnore: []string{"skip_delete", "deletion_policy"},
233234
},
234235
},
235236
})
@@ -352,6 +353,65 @@ func testAccCheckGoogleProjectHasNoLabels(t *testing.T, r, pid string) resource.
352353
}
353354
}
354355

356+
func TestAccProject_noAllowDestroy(t *testing.T) {
357+
t.Parallel()
358+
359+
org := envvar.GetTestOrgFromEnv(t)
360+
pid := fmt.Sprintf("%s-%d", TestPrefix, acctest.RandInt(t))
361+
acctest.VcrTest(t, resource.TestCase{
362+
PreCheck: func() { acctest.AccTestPreCheck(t) },
363+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
364+
Steps: []resource.TestStep{
365+
{
366+
Config: testAccProject_noAllowDestroy(pid, org),
367+
},
368+
{
369+
ResourceName: "google_project.acceptance",
370+
ImportState: true,
371+
ImportStateVerify: true,
372+
ImportStateVerifyIgnore: []string{"skip_delete", "deletion_policy"},
373+
},
374+
{
375+
Config: testAccProject_noAllowDestroy(pid, org),
376+
Destroy: true,
377+
ExpectError: regexp.MustCompile("deletion_policy"),
378+
},
379+
{
380+
Config: testAccProject_create(pid, org),
381+
},
382+
},
383+
})
384+
}
385+
386+
func TestAccProject_abandon(t *testing.T) {
387+
t.Parallel()
388+
389+
org := envvar.GetTestOrgFromEnv(t)
390+
pid := fmt.Sprintf("%s-%d", TestPrefix, acctest.RandInt(t))
391+
acctest.VcrTest(t, resource.TestCase{
392+
PreCheck: func() { acctest.AccTestPreCheck(t) },
393+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
394+
Steps: []resource.TestStep{
395+
{
396+
Config: testAccProject_abandon(pid, org),
397+
},
398+
{
399+
ResourceName: "google_project.acceptance",
400+
ImportState: true,
401+
ImportStateVerify: true,
402+
ImportStateVerifyIgnore: []string{"deletion_policy"},
403+
},
404+
{
405+
Config: testAccProject_abandon(pid, org),
406+
Destroy: true,
407+
Check: resource.ComposeTestCheckFunc(
408+
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
409+
),
410+
},
411+
},
412+
})
413+
}
414+
355415
func testAccProject_createWithoutOrg(pid string) string {
356416
return fmt.Sprintf(`
357417
resource "google_project" "acceptance" {
@@ -361,6 +421,28 @@ resource "google_project" "acceptance" {
361421
`, pid, pid)
362422
}
363423

424+
func testAccProject_noAllowDestroy(pid, org string) string {
425+
return fmt.Sprintf(`
426+
resource "google_project" "acceptance" {
427+
project_id = "%s"
428+
name = "%s"
429+
org_id = "%s"
430+
deletion_policy = "PREVENT"
431+
}
432+
`, pid, pid, org)
433+
}
434+
435+
func testAccProject_abandon(pid, org string) string {
436+
return fmt.Sprintf(`
437+
resource "google_project" "acceptance" {
438+
project_id = "%s"
439+
name = "%s"
440+
org_id = "%s"
441+
deletion_policy = "ABANDON"
442+
}
443+
`, pid, pid, org)
444+
}
445+
364446
func testAccProject_createBilling(pid, org, billing string) string {
365447
return fmt.Sprintf(`
366448
resource "google_project" "acceptance" {

website/docs/r/google_project.html.markdown

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ The following arguments are supported:
9898
`false`. Note that when `false`, Terraform enables `compute.googleapis.com` on the project to interact
9999
with the GCE API and currently leaves it enabled.
100100

101+
* `deletion_policy` - (Optional) The deletion policy for the Project. Setting PREVENT will protect the project
102+
against any destroy actions caused by a terraform apply or terraform destroy. Setting ABANDON allows the resource
103+
to be abandoned rather than deleted, i.e., the Terraform resource can be deleted without deleting the Project via
104+
the Google API. Possible values are: "PREVENT", "ABANDON", "DELETE". Default value is `DELETE`.
105+
101106
## Attributes Reference
102107

103108
In addition to the arguments listed above, the following computed attributes are

0 commit comments

Comments
 (0)