diff --git a/.changelog/12116.txt b/.changelog/12116.txt new file mode 100644 index 00000000000..2a13e2e6c7b --- /dev/null +++ b/.changelog/12116.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +workflows: added `deletion_protection` field to `google_workflows_workflow` resource +``` \ No newline at end of file diff --git a/google/services/workflows/resource_workflows_workflow.go b/google/services/workflows/resource_workflows_workflow.go index 75db8259139..753c3f48787 100644 --- a/google/services/workflows/resource_workflows_workflow.go +++ b/google/services/workflows/resource_workflows_workflow.go @@ -164,6 +164,17 @@ Modifying this field for an existing workflow results in a new workflow revision Computed: true, Description: `The timestamp of when the workflow was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, }, + "deletion_protection": { + Type: schema.TypeBool, + Optional: true, + Description: `Whether Terraform will be prevented from destroying the workflow. Defaults to true. +When a'terraform destroy' or 'terraform apply' would delete the workflow, +the command will fail if this field is not set to false in Terraform state. +When the field is set to true or unset in Terraform state, a 'terraform apply' +or 'terraform destroy' that would delete the workflow will fail. +When the field is set to false, deleting the workflow is allowed.`, + Default: true, + }, "name_prefix": { Type: schema.TypeString, Optional: true, @@ -352,6 +363,12 @@ func resourceWorkflowsWorkflowRead(d *schema.ResourceData, meta interface{}) err return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("WorkflowsWorkflow %q", d.Id())) } + // Explicitly set virtual fields to default values if unset + if _, ok := d.GetOkExists("deletion_protection"); !ok { + if err := d.Set("deletion_protection", true); err != nil { + return fmt.Errorf("Error setting deletion_protection: %s", err) + } + } if err := d.Set("project", project); err != nil { return fmt.Errorf("Error reading Workflow: %s", err) } @@ -573,6 +590,9 @@ func resourceWorkflowsWorkflowDelete(d *schema.ResourceData, meta interface{}) e } headers := make(http.Header) + if d.Get("deletion_protection").(bool) { + return fmt.Errorf("cannot destroy workflow without setting deletion_protection=false and running `terraform apply`") + } log.Printf("[DEBUG] Deleting Workflow %q", d.Id()) res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ diff --git a/google/services/workflows/resource_workflows_workflow_generated_test.go b/google/services/workflows/resource_workflows_workflow_generated_test.go index 1faf9532af7..3edff64549c 100644 --- a/google/services/workflows/resource_workflows_workflow_generated_test.go +++ b/google/services/workflows/resource_workflows_workflow_generated_test.go @@ -68,6 +68,7 @@ resource "google_workflows_workflow" "example" { user_env_vars = { url = "https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam" } + deletion_protection = false source_contents = <<-EOF # This is a sample workflow. You can replace it with your source code. # diff --git a/google/services/workflows/resource_workflows_workflow_sweeper.go b/google/services/workflows/resource_workflows_workflow_sweeper.go deleted file mode 100644 index 8d860d27421..00000000000 --- a/google/services/workflows/resource_workflows_workflow_sweeper.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// ---------------------------------------------------------------------------- -// -// *** AUTO GENERATED CODE *** Type: MMv1 *** -// -// ---------------------------------------------------------------------------- -// -// This file is automatically generated by Magic Modules and manual -// changes will be clobbered when the file is regenerated. -// -// Please read more about how to change this file in -// .github/CONTRIBUTING.md. -// -// ---------------------------------------------------------------------------- - -package workflows - -import ( - "context" - "log" - "strings" - "testing" - - "github.com/hashicorp/terraform-provider-google/google/envvar" - "github.com/hashicorp/terraform-provider-google/google/sweeper" - "github.com/hashicorp/terraform-provider-google/google/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport" -) - -func init() { - sweeper.AddTestSweepers("WorkflowsWorkflow", testSweepWorkflowsWorkflow) -} - -// At the time of writing, the CI only passes us-central1 as the region -func testSweepWorkflowsWorkflow(region string) error { - resourceName := "WorkflowsWorkflow" - log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) - - config, err := sweeper.SharedConfigForRegion(region) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) - return err - } - - err = config.LoadAndValidate(context.Background()) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) - return err - } - - t := &testing.T{} - billingId := envvar.GetTestBillingAccountFromEnv(t) - - // Setup variables to replace in list template - d := &tpgresource.ResourceDataMock{ - FieldsInSchema: map[string]interface{}{ - "project": config.Project, - "region": region, - "location": region, - "zone": "-", - "billing_account": billingId, - }, - } - - listTemplate := strings.Split("https://workflows.googleapis.com/v1/projects/{{project}}/locations/{{region}}/workflows", "?")[0] - listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) - return nil - } - - res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: config.Project, - RawURL: listUrl, - UserAgent: config.UserAgent, - }) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) - return nil - } - - resourceList, ok := res["workflows"] - if !ok { - log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") - return nil - } - - rl := resourceList.([]interface{}) - - log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) - // Keep count of items that aren't sweepable for logging. - nonPrefixCount := 0 - for _, ri := range rl { - obj := ri.(map[string]interface{}) - if obj["name"] == nil { - log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) - return nil - } - - name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) - // Skip resources that shouldn't be sweeped - if !sweeper.IsSweepableTestResource(name) { - nonPrefixCount++ - continue - } - - deleteTemplate := "https://workflows.googleapis.com/v1/projects/{{project}}/locations/{{region}}/workflows/{{name}}" - deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) - return nil - } - deleteUrl = deleteUrl + name - - // Don't wait on operations as we may have a lot to delete - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "DELETE", - Project: config.Project, - RawURL: deleteUrl, - UserAgent: config.UserAgent, - }) - if err != nil { - log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) - } else { - log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) - } - } - - if nonPrefixCount > 0 { - log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) - } - - return nil -} diff --git a/google/services/workflows/resource_workflows_workflow_test.go b/google/services/workflows/resource_workflows_workflow_test.go index a9ba6213aa4..52f9c371c5e 100644 --- a/google/services/workflows/resource_workflows_workflow_test.go +++ b/google/services/workflows/resource_workflows_workflow_test.go @@ -45,6 +45,7 @@ resource "google_workflows_workflow" "example" { user_env_vars = { url = "https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam" } + deletion_protection = false source_contents = <<-EOF # This is a sample workflow, feel free to replace it with your source code # @@ -86,6 +87,126 @@ resource "google_workflows_workflow" "example" { user_env_vars = { url = "https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam" } + deletion_protection = false + source_contents = <<-EOF + # This is a sample workflow, feel free to replace it with your source code + # + # This workflow does the following: + # - reads current time and date information from an external API and stores + # the response in CurrentDateTime variable + # - retrieves a list of Wikipedia articles related to the day of the week + # from CurrentDateTime + # - returns the list of articles as an output of the workflow + # FYI, In terraform you need to escape the $$ or it will cause errors. + + - getCurrentTime: + call: http.get + args: + url: $${sys.get_env("url")} + result: CurrentDateTime + - readWikipedia: + call: http.get + args: + url: https:/fi.wikipedia.org/w/api.php + query: + action: opensearch + search: $${CurrentDateTime.body.dayOfTheWeek} + result: WikiResult + - returnOutput: + return: $${WikiResult.body[1]} +EOF +} +`, name) +} + +func TestAccWorkflowsWorkflow_UpdateDeletionProtectionFalseToTrue(t *testing.T) { + // Custom test written to test diffs + t.Parallel() + + workflowName := fmt.Sprintf("tf-test-acc-workflow-%d", acctest.RandInt(t)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckWorkflowsWorkflowDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccWorkflowsWorkflow_Basic_DeletionProtectionFalse(workflowName), + }, + { + Config: testAccWorkflowsWorkflow_Basic_DeletionProtectionTrue(workflowName), + }, + { + Config: testAccWorkflowsWorkflow_Basic_DeletionProtectionFalse(workflowName), + }, + }, + }) +} + +func TestAccWorkflowsWorkflow_UpdateDeletionProtectionTrueToFalse(t *testing.T) { + // Custom test written to test diffs + t.Parallel() + + workflowName := fmt.Sprintf("tf-test-acc-workflow-%d", acctest.RandInt(t)) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckWorkflowsWorkflowDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccWorkflowsWorkflow_Basic_DeletionProtectionTrue(workflowName), + }, + { + Config: testAccWorkflowsWorkflow_Basic_DeletionProtectionFalse(workflowName), + }, + }, + }) +} + +func testAccWorkflowsWorkflow_Basic_DeletionProtectionFalse(name string) string { + return fmt.Sprintf(` +resource "google_workflows_workflow" "example" { + name = "%s" + region = "us-central1" + deletion_protection = false + source_contents = <<-EOF + # This is a sample workflow, feel free to replace it with your source code + # + # This workflow does the following: + # - reads current time and date information from an external API and stores + # the response in CurrentDateTime variable + # - retrieves a list of Wikipedia articles related to the day of the week + # from CurrentDateTime + # - returns the list of articles as an output of the workflow + # FYI, In terraform you need to escape the $$ or it will cause errors. + + - getCurrentTime: + call: http.get + args: + url: $${sys.get_env("url")} + result: CurrentDateTime + - readWikipedia: + call: http.get + args: + url: https://en.wikipedia.org/w/api.php + query: + action: opensearch + search: $${CurrentDateTime.body.dayOfTheWeek} + result: WikiResult + - returnOutput: + return: $${WikiResult.body[1]} +EOF +} +`, name) +} + +func testAccWorkflowsWorkflow_Basic_DeletionProtectionTrue(name string) string { + return fmt.Sprintf(` +resource "google_workflows_workflow" "example" { + name = "%s" + region = "us-central1" + deletion_protection = true source_contents = <<-EOF # This is a sample workflow, feel free to replace it with your source code # @@ -190,6 +311,7 @@ resource "google_workflows_workflow" "example" { name = "%s" region = "us-central1" description = "Magic" + deletion_protection = false crypto_key_name = "%s" source_contents = <<-EOF # This is a sample workflow, feel free to replace it with your source code diff --git a/website/docs/r/workflows_workflow.html.markdown b/website/docs/r/workflows_workflow.html.markdown index 01944d36df9..fe9611a548b 100644 --- a/website/docs/r/workflows_workflow.html.markdown +++ b/website/docs/r/workflows_workflow.html.markdown @@ -54,6 +54,7 @@ resource "google_workflows_workflow" "example" { user_env_vars = { url = "https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam" } + deletion_protection = false source_contents = <<-EOF # This is a sample workflow. You can replace it with your source code. # @@ -146,6 +147,13 @@ The following arguments are supported: * `project` - (Optional) The ID of the project in which the resource belongs. If it is not provided, the provider project is used. +* `deletion_protection` - (Optional) Whether Terraform will be prevented from destroying the workflow. Defaults to true. +When a`terraform destroy` or `terraform apply` would delete the workflow, +the command will fail if this field is not set to false in Terraform state. +When the field is set to true or unset in Terraform state, a `terraform apply` +or `terraform destroy` that would delete the workflow will fail. +When the field is set to false, deleting the workflow is allowed. + * `name_prefix` - (Optional) Creates a unique name beginning with the specified prefix. If this and name are unspecified, a random value is chosen for the name.