Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new datasource: sql databases #13738

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/7055.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-datasource
google_sql_databases
```
89 changes: 89 additions & 0 deletions google/data_source_sql_databases.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package google

import (
"fmt"
"sort"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
sqladmin "google.golang.org/api/sqladmin/v1beta4"
)

func dataSourceSqlDatabases() *schema.Resource {

return &schema.Resource{
Read: dataSourceSqlDatabasesRead,

Schema: map[string]*schema.Schema{
"project": {
Type: schema.TypeString,
Optional: true,
Description: `Project ID of the project that contains the instance.`,
},
"instance": {
Type: schema.TypeString,
Required: true,
Description: `The name of the Cloud SQL database instance in which the database belongs.`,
},
"databases": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: datasourceSchemaFromResourceSchema(resourceSQLDatabase().Schema),
},
},
},
}
}

func dataSourceSqlDatabasesRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
userAgent, err := generateUserAgentString(d, config.userAgent)
if err != nil {
return err
}
project, err := getProject(d, config)
if err != nil {
return err
}
var databases *sqladmin.DatabasesListResponse
err = retryTimeDuration(func() (rerr error) {
databases, rerr = config.NewSqlAdminClient(userAgent).Databases.List(project, d.Get("instance").(string)).Do()
return rerr
}, d.Timeout(schema.TimeoutRead), isSqlOperationInProgressError)

if err != nil {
return handleNotFoundError(err, d, fmt.Sprintf("Databases in %q instance", d.Get("instance").(string)))
}
flattenedDatabases := flattenDatabases(databases.Items)

//client-side sorting to provide consistent ordering of the databases
sort.SliceStable(flattenedDatabases, func(i, j int) bool {
return strings.Compare(flattenedDatabases[i]["name"].(string), flattenedDatabases[j]["name"].(string)) < 1
})
if err := d.Set("databases", flattenedDatabases); err != nil {
return fmt.Errorf("Error setting databases: %s", err)
}
d.SetId(fmt.Sprintf("project/%s/instance/%s/databases", project, d.Get("instance").(string)))
return nil
}

func flattenDatabases(fetchedDatabases []*sqladmin.Database) []map[string]interface{} {
if fetchedDatabases == nil {
return make([]map[string]interface{}, 0)
}

databases := make([]map[string]interface{}, 0, len(fetchedDatabases))
for _, rawDatabase := range fetchedDatabases {
database := make(map[string]interface{})
database["name"] = rawDatabase.Name
database["instance"] = rawDatabase.Instance
database["project"] = rawDatabase.Project
database["charset"] = rawDatabase.Charset
database["collation"] = rawDatabase.Collation
database["self_link"] = rawDatabase.SelfLink

databases = append(databases, database)
}
return databases
}
152 changes: 152 additions & 0 deletions google/data_source_sql_databases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package google

import (
"errors"
"fmt"
"strconv"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestAccDataSourceSqlDatabases_basic(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"random_suffix": randString(t, 10),
}

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccSqlDatabaseDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccDataSourceSqlDatabases_basic(context),
Check: resource.ComposeTestCheckFunc(
checkDatabasesListDataSourceStateMatchesResourceStateWithIgnores(
"data.google_sql_databases.qa",
"google_sql_database.db1",
"google_sql_database.db2",
map[string]struct{}{
"deletion_policy": {},
"id": {},
},
),
),
},
},
})
}

func testAccDataSourceSqlDatabases_basic(context map[string]interface{}) string {
return Nprintf(`
resource "google_sql_database_instance" "main" {
name = "tf-test-instance-%{random_suffix}"
database_version = "POSTGRES_14"
region = "us-central1"

settings {
tier = "db-f1-micro"
}

deletion_protection = false
}

resource "google_sql_database" "db1"{
instance = google_sql_database_instance.main.name
name = "pg-db1"
}

resource "google_sql_database" "db2"{
instance = google_sql_database_instance.main.name
name = "pg-db2"
}

data "google_sql_databases" "qa" {
instance = google_sql_database_instance.main.name
depends_on = [
google_sql_database.db1,
google_sql_database.db2
]
}
`, context)
}

// This function checks data source state matches for resorceName database instance state
func checkDatabasesListDataSourceStateMatchesResourceStateWithIgnores(dataSourceName, resourceName, resourceName2 string, ignoreFields map[string]struct{}) func(*terraform.State) error {
return func(s *terraform.State) error {
ds, ok := s.RootModule().Resources[dataSourceName]
if !ok {
return fmt.Errorf("can't find %s in state", dataSourceName)
}

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("can't find %s in state", resourceName)
}

rs2, ok := s.RootModule().Resources[resourceName2]
if !ok {
return fmt.Errorf("can't find %s in state", resourceName2)
}

dsAttr := ds.Primary.Attributes
rsAttr := rs.Primary.Attributes
rsAttr2 := rs2.Primary.Attributes

err := checkDatabaseFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr, ignoreFields)
if err != nil {
return err
}
err = checkDatabaseFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr2, ignoreFields)
return err

}
}

// This function checks whether all the attributes of the database instance resource and the attributes of the datbase instance inside the data source list are the same
func checkDatabaseFieldsMatchForDataSourceStateAndResourceState(dsAttr, rsAttr map[string]string, ignoreFields map[string]struct{}) error {
totalInstances, err := strconv.Atoi(dsAttr["databases.#"])
if err != nil {
return errors.New("Couldn't convert length of instances list to integer")
}
index := "-1"
for i := 0; i < totalInstances; i++ {
if dsAttr["databases."+strconv.Itoa(i)+".name"] == rsAttr["name"] {
index = strconv.Itoa(i)
}
}

if index == "-1" {
return errors.New("The newly created intance is not found in the data source")
}

errMsg := ""
// Data sources are often derived from resources, so iterate over the resource fields to
// make sure all fields are accounted for in the data source.
// If a field exists in the data source but not in the resource, its expected value should
// be checked separately.
for k := range rsAttr {
if _, ok := ignoreFields[k]; ok {
continue
}
if k == "%" {
continue
}
if dsAttr["databases."+index+"."+k] != rsAttr[k] {
// ignore data sources where an empty list is being compared against a null list.
if k[len(k)-1:] == "#" && (dsAttr["databases."+index+"."+k] == "" || dsAttr["databases."+index+"."+k] == "0") && (rsAttr[k] == "" || rsAttr[k] == "0") {
continue
}
errMsg += fmt.Sprintf("%s is %s; want %s\n", k, dsAttr["databases."+index+"."+k], rsAttr[k])
}
}

if errMsg != "" {
return errors.New(errMsg)
}

return nil
}
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ func Provider() *schema.Provider {
"google_spanner_instance": dataSourceSpannerInstance(),
"google_sql_ca_certs": dataSourceGoogleSQLCaCerts(),
"google_sql_backup_run": dataSourceSqlBackupRun(),
"google_sql_databases": dataSourceSqlDatabases(),
"google_sql_database": dataSourceSqlDatabase(),
"google_sql_database_instance": dataSourceSqlDatabaseInstance(),
"google_sql_database_instances": dataSourceSqlDatabaseInstances(),
Expand Down
31 changes: 31 additions & 0 deletions website/docs/d/sql_databases.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
subcategory: "Cloud SQL"
page_title: "Google: google_sql_databases"
description: |-
Get a list of databases in a Cloud SQL database instance.
---

# google\_sql\_databases

Use this data source to get information about a list of databases in a Cloud SQL instance.
## Example Usage


```hcl
data "google_sql_databases" "qa" {
instance = google_sql_database_instance.main.name
}
```

## Argument Reference

The following arguments are supported:

* `instance` - (required) The name of the Cloud SQL database instance in which the database belongs.

* `project` - (optional) The ID of the project in which the instance belongs.

-> **Note** This datasource performs client-side sorting to provide consistent ordering of the databases.

## Attributes Reference
See [google_sql_database](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/sql_database) resource for details of all the available attributes.