Skip to content

Commit

Permalink
Don't run sql precheck if projects.get call fails (#4141) (#7622)
Browse files Browse the repository at this point in the history
Signed-off-by: Modular Magician <[email protected]>
  • Loading branch information
modular-magician authored Oct 23, 2020
1 parent 246a552 commit 1671926
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .changelog/4141.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
sql: fixed a case in `google_sql_database_instance` where we inadvertently required the `projects.get` permission for a service networking precheck introduced in `v3.44.0`
```
6 changes: 4 additions & 2 deletions google/resource_service_networking_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,12 @@ func retrieveServiceNetworkingNetworkName(d *schema.ResourceData, config *Config
if pid == "" {
return "", fmt.Errorf("Could not determine project")
}

log.Printf("[DEBUG] Retrieving project number by doing a GET with the project id, as required by service networking")
project, err := config.NewResourceManagerClient(userAgent).Projects.Get(pid).Do()
if err != nil {
return "", fmt.Errorf("Failed to retrieve project, pid: %s, err: %s", pid, err)
// note: returning a wrapped error is part of this method's contract!
// https://blog.golang.org/go1.13-errors
return "", fmt.Errorf("Failed to retrieve project, pid: %s, err: %w", pid, err)
}

networkName := networkFieldValue.Name
Expand Down
47 changes: 34 additions & 13 deletions google/resource_sql_database_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package google

import (
"context"
"errors"
"fmt"
"log"
"regexp"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"

"google.golang.org/api/googleapi"
sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

Expand Down Expand Up @@ -651,24 +653,14 @@ func resourceSqlDatabaseInstanceCreate(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error setting name: %s", err)
}

// Before we create the instance, check if at least 1 service connection exists for private SQL Instances.
// SQL Instances that fail to create are expensive- see https://github.com/hashicorp/terraform-provider-google/issues/7154
// We can fail fast to stop instance names from getting reserved.
network := d.Get("settings.0.ip_configuration.0.private_network").(string)
if network != "" {
// Borrow some of the functions from resource_service_networking_connection.go
serviceNetworkingNetworkName, err := retrieveServiceNetworkingNetworkName(d, config, network, userAgent)
err = sqlDatabaseInstanceServiceNetworkPrecheck(d, config, userAgent, network)
if err != nil {
return err
}
response, err := config.NewServiceNetworkingClient(userAgent).Services.Connections.List("services/servicenetworking.googleapis.com").
Network(serviceNetworkingNetworkName).Do()
if err != nil {
// It is possible that the identity creating the SQL Instance might not have permissions to call servicenetworking.services.connections.list
log.Printf("[WARNING] Failed to list Service Networking of the project. Skipping Service Networking check.")
} else {
if len(response.Connections) < 1 {
return fmt.Errorf("Error, failed to create instance because the network doesn't have at least 1 private services connection. Please see https://cloud.google.com/sql/docs/mysql/private-ip#network_requirements for how to create this connection.")
}
}
}

instance := &sqladmin.DatabaseInstance{
Expand Down Expand Up @@ -1297,3 +1289,32 @@ func sqlDatabaseIsMaster(d *schema.ResourceData) bool {
_, ok := d.GetOk("master_instance_name")
return !ok
}

func sqlDatabaseInstanceServiceNetworkPrecheck(d *schema.ResourceData, config *Config, userAgent, network string) error {
log.Printf("[DEBUG] checking network %q for at least one service networking connection", network)
// This call requires projects.get permissions, which may not have been granted to the Terraform actor,
// particularly in shared VPC setups. Most will! But it's not strictly required.
serviceNetworkingNetworkName, err := retrieveServiceNetworkingNetworkName(d, config, network, userAgent)
if err != nil {
var gerr *googleapi.Error
if errors.As(err, &gerr) {
log.Printf("[DEBUG] retrieved googleapi error while creating sn name for %q. precheck skipped. code %v and message: %s", network, gerr.Code, gerr.Body)
return nil
}

return err
}

response, err := config.NewServiceNetworkingClient(userAgent).Services.Connections.List("services/servicenetworking.googleapis.com").Network(serviceNetworkingNetworkName).Do()
if err != nil {
// It is possible that the actor creating the SQL Instance might not have permissions to call servicenetworking.services.connections.list
log.Printf("[WARNING] Failed to list Service Networking of the project. Skipped Service Networking precheck.")
return nil
}

if len(response.Connections) < 1 {
return fmt.Errorf("Error, failed to create instance because the network doesn't have at least 1 private services connection. Please see https://cloud.google.com/sql/docs/mysql/private-ip#network_requirements for how to create this connection.")
}

return nil
}

0 comments on commit 1671926

Please sign in to comment.