From 5eaf92787a78171fa86887075d74c8d2db825ceb Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Thu, 1 Dec 2022 23:44:26 +0000 Subject: [PATCH] Datastream check private connection state after create and update (#6862) * Add desired state field to make sure PC is created in CREATED - b/260057913 * Revert virtual field * Add state field check to make sure PC is created in CREATED - b/260057913 * Add error details and message in failure cases. * Fix message formatting * Added a post import impl Signed-off-by: Modular Magician --- .changelog/6862.txt | 3 + .../resource_datastream_private_connection.go | 100 ++++++++++++++++++ ...atastream_private_connection.html.markdown | 17 +++ 3 files changed, 120 insertions(+) create mode 100644 .changelog/6862.txt diff --git a/.changelog/6862.txt b/.changelog/6862.txt new file mode 100644 index 00000000000..ec84d62f7bb --- /dev/null +++ b/.changelog/6862.txt @@ -0,0 +1,3 @@ +```release-note:bug +datastream: fixed `google_datastream_private_connection` ignoring failures during creation +``` diff --git a/google/resource_datastream_private_connection.go b/google/resource_datastream_private_connection.go index b8775f4844a..792f805d51e 100644 --- a/google/resource_datastream_private_connection.go +++ b/google/resource_datastream_private_connection.go @@ -15,14 +15,48 @@ package google import ( + "encoding/json" "fmt" "log" "reflect" "time" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +func extractError(d *schema.ResourceData) error { + // Casts are not safe since the logic that populate it is type deterministic. + error := d.Get("error").([]interface{})[0].(map[string]interface{}) + message := error["message"].(string) + details := error["details"].(map[string]interface{}) + detailsJSON, _ := json.Marshal(details) + return fmt.Errorf("Failed to create PrivateConnection. %s details = %s", message, string(detailsJSON)) +} + +// waitForPrivateConnectionReady waits for a private connection state to become +// CREATED, if the state is FAILED propegate the error to the user. +func waitForPrivateConnectionReady(d *schema.ResourceData, config *Config, timeout time.Duration) error { + return resource.Retry(timeout, func() *resource.RetryError { + if err := resourceDatastreamPrivateConnectionRead(d, config); err != nil { + return resource.NonRetryableError(err) + } + + name := d.Get("name").(string) + state := d.Get("state").(string) + if state == "CREATING" { + return resource.RetryableError(fmt.Errorf("PrivateConnection %q has state %q.", name, state)) + } else if state == "CREATED" { + log.Printf("[DEBUG] PrivateConnection %q has state %q.", name, state) + return nil + } else if state == "FAILED" { + return resource.NonRetryableError(extractError(d)) + } else { + return resource.NonRetryableError(fmt.Errorf("PrivateConnection %q has state %q.", name, state)) + } + }) +} + func resourceDatastreamPrivateConnection() *schema.Resource { return &schema.Resource{ Create: resourceDatastreamPrivateConnectionCreate, @@ -89,11 +123,36 @@ Format: projects/{project}/global/{networks}/{name}`, Description: `Labels.`, Elem: &schema.Schema{Type: schema.TypeString}, }, + "error": { + Type: schema.TypeList, + Computed: true, + Description: `The PrivateConnection error in case of failure.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "details": { + Type: schema.TypeMap, + Optional: true, + Description: `A list of messages that carry the error details.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "message": { + Type: schema.TypeString, + Optional: true, + Description: `A message containing more information about the error that occurred.`, + }, + }, + }, + }, "name": { Type: schema.TypeString, Computed: true, Description: `The resource's name.`, }, + "state": { + Type: schema.TypeString, + Computed: true, + Description: `State of the PrivateConnection.`, + }, "project": { Type: schema.TypeString, Optional: true, @@ -187,6 +246,10 @@ func resourceDatastreamPrivateConnectionCreate(d *schema.ResourceData, meta inte } d.SetId(id) + if err := waitForPrivateConnectionReady(d, config, d.Timeout(schema.TimeoutCreate)-time.Minute); err != nil { + return fmt.Errorf("Error waiting for PrivateConnection %q to be CREATED. %q", d.Get("name").(string), err) + } + log.Printf("[DEBUG] Finished creating PrivateConnection %q: %#v", d.Id(), res) return resourceDatastreamPrivateConnectionRead(d, meta) @@ -235,6 +298,12 @@ func resourceDatastreamPrivateConnectionRead(d *schema.ResourceData, meta interf if err := d.Set("display_name", flattenDatastreamPrivateConnectionDisplayName(res["displayName"], d, config)); err != nil { return fmt.Errorf("Error reading PrivateConnection: %s", err) } + if err := d.Set("state", flattenDatastreamPrivateConnectionState(res["state"], d, config)); err != nil { + return fmt.Errorf("Error reading PrivateConnection: %s", err) + } + if err := d.Set("error", flattenDatastreamPrivateConnectionError(res["error"], d, config)); err != nil { + return fmt.Errorf("Error reading PrivateConnection: %s", err) + } if err := d.Set("vpc_peering_config", flattenDatastreamPrivateConnectionVPCPeeringConfig(res["vpcPeeringConfig"], d, config)); err != nil { return fmt.Errorf("Error reading PrivateConnection: %s", err) } @@ -304,6 +373,10 @@ func resourceDatastreamPrivateConnectionImport(d *schema.ResourceData, meta inte } d.SetId(id) + if err := waitForPrivateConnectionReady(d, config, d.Timeout(schema.TimeoutCreate)-time.Minute); err != nil { + return nil, fmt.Errorf("Error waiting for PrivateConnection %q to be CREATED during importing: %q", d.Get("name").(string), err) + } + return []*schema.ResourceData{d}, nil } @@ -319,6 +392,33 @@ func flattenDatastreamPrivateConnectionDisplayName(v interface{}, d *schema.Reso return v } +func flattenDatastreamPrivateConnectionState(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamPrivateConnectionError(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["message"] = + flattenDatastreamPrivateConnectionErrorMessage(original["message"], d, config) + transformed["details"] = + flattenDatastreamPrivateConnectionErrorDetails(original["details"], d, config) + return []interface{}{transformed} +} +func flattenDatastreamPrivateConnectionErrorMessage(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenDatastreamPrivateConnectionErrorDetails(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + func flattenDatastreamPrivateConnectionVPCPeeringConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { if v == nil { return nil diff --git a/website/docs/r/datastream_private_connection.html.markdown b/website/docs/r/datastream_private_connection.html.markdown index 65ec5052c6a..3fda665a364 100644 --- a/website/docs/r/datastream_private_connection.html.markdown +++ b/website/docs/r/datastream_private_connection.html.markdown @@ -113,6 +113,23 @@ In addition to the arguments listed above, the following computed attributes are * `name` - The resource's name. +* `state` - + State of the PrivateConnection. + +* `error` - + The PrivateConnection error in case of failure. + Structure is [documented below](#nested_error). + + +The `error` block contains: + +* `message` - + (Optional) + A message containing more information about the error that occurred. + +* `details` - + (Optional) + A list of messages that carry the error details. ## Timeouts