From 752b406783e405fd37b8cb773f7d9f8c500069cd Mon Sep 17 00:00:00 2001 From: Nicki Watt Date: Mon, 14 Aug 2017 17:53:11 +0100 Subject: [PATCH] Add Google Spanner Support (google_spanner_database) (#271) --- google/import_spanner_database_test.go | 57 ++++ google/provider.go | 1 + google/resource_spanner_database.go | 244 +++++++++++++ google/resource_spanner_database_test.go | 321 ++++++++++++++++++ google/spanner_database_operation.go | 62 ++++ website/docs/r/spanner_database.html.markdown | 71 ++++ website/google.erb | 4 + 7 files changed, 760 insertions(+) create mode 100644 google/import_spanner_database_test.go create mode 100644 google/resource_spanner_database.go create mode 100644 google/resource_spanner_database_test.go create mode 100644 google/spanner_database_operation.go create mode 100644 website/docs/r/spanner_database.html.markdown diff --git a/google/import_spanner_database_test.go b/google/import_spanner_database_test.go new file mode 100644 index 00000000000..e4abd9a15f8 --- /dev/null +++ b/google/import_spanner_database_test.go @@ -0,0 +1,57 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccSpannerDatabase_importInstanceDatabase(t *testing.T) { + resourceName := "google_spanner_database.basic" + instanceName := fmt.Sprintf("span-iname-%s", acctest.RandString(10)) + dbName := fmt.Sprintf("span-dbname-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSpannerDatabaseDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccSpannerDatabase_basicImport(instanceName, dbName), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportStateId: instanceName + "/" + dbName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSpannerDatabase_importProjectInstanceDatabase(t *testing.T) { + resourceName := "google_spanner_database.basic" + instanceName := fmt.Sprintf("span-iname-%s", acctest.RandString(10)) + dbName := fmt.Sprintf("span-dbname-%s", acctest.RandString(10)) + var projectId = multiEnvSearch([]string{"GOOGLE_PROJECT", "GCLOUD_PROJECT", "CLOUDSDK_CORE_PROJECT"}) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSpannerDatabaseDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccSpannerDatabase_basicImportWithProject(projectId, instanceName, dbName), + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/google/provider.go b/google/provider.go index 54bb981ecc1..954eab3eb4b 100644 --- a/google/provider.go +++ b/google/provider.go @@ -112,6 +112,7 @@ func Provider() terraform.ResourceProvider { "google_dns_record_set": resourceDnsRecordSet(), "google_sourcerepo_repository": resourceSourceRepoRepository(), "google_spanner_instance": resourceSpannerInstance(), + "google_spanner_database": resourceSpannerDatabase(), "google_sql_database": resourceSqlDatabase(), "google_sql_database_instance": resourceSqlDatabaseInstance(), "google_sql_user": resourceSqlUser(), diff --git a/google/resource_spanner_database.go b/google/resource_spanner_database.go new file mode 100644 index 00000000000..fe8e342b0f4 --- /dev/null +++ b/google/resource_spanner_database.go @@ -0,0 +1,244 @@ +package google + +import ( + "fmt" + "log" + "net/http" + "regexp" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + + "google.golang.org/api/googleapi" + "google.golang.org/api/spanner/v1" +) + +func resourceSpannerDatabase() *schema.Resource { + return &schema.Resource{ + Create: resourceSpannerDatabaseCreate, + Read: resourceSpannerDatabaseRead, + Delete: resourceSpannerDatabaseDelete, + Importer: &schema.ResourceImporter{ + State: resourceSpannerDatabaseImportState, + }, + + Schema: map[string]*schema.Schema{ + + "instance": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) < 2 && len(value) > 30 { + errors = append(errors, fmt.Errorf( + "%q must be between 2 and 30 characters in length", k)) + } + if !regexp.MustCompile("^[a-z0-9-]+$").MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q can only contain lowercase letters, numbers and hyphens", k)) + } + if !regexp.MustCompile("^[a-z]").MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q must start with a letter", k)) + } + if !regexp.MustCompile("[a-z0-9]$").MatchString(value) { + errors = append(errors, fmt.Errorf( + "%q must end with a number or a letter", k)) + } + return + }, + }, + + "project": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ddl": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "state": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceSpannerDatabaseCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + id, err := buildSpannerDatabaseId(d, config) + if err != nil { + return err + } + + cdr := &spanner.CreateDatabaseRequest{} + cdr.CreateStatement = fmt.Sprintf("CREATE DATABASE `%s`", id.Database) + if v, ok := d.GetOk("ddl"); ok { + cdr.ExtraStatements = convertStringArr(v.([]interface{})) + } + + op, err := config.clientSpanner.Projects.Instances.Databases.Create( + id.parentInstanceUri(), cdr).Do() + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusConflict { + return fmt.Errorf("Error, A database with name %s already exists in this instance", id.Database) + } + return fmt.Errorf("Error, failed to create database %s: %s", id.Database, err) + } + + d.SetId(id.terraformId()) + + // Wait until it's created + timeoutMins := int(d.Timeout(schema.TimeoutCreate).Minutes()) + waitErr := spannerDatabaseOperationWait(config, op, "Creating Spanner database", timeoutMins) + if waitErr != nil { + // The resource didn't actually create + d.SetId("") + return waitErr + } + + log.Printf("[INFO] Spanner database %s has been created", id.terraformId()) + return resourceSpannerDatabaseRead(d, meta) +} + +func resourceSpannerDatabaseRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + id, err := buildSpannerDatabaseId(d, config) + if err != nil { + return err + } + + db, err := config.clientSpanner.Projects.Instances.Databases.Get( + id.databaseUri()).Do() + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("Spanner database %q", id.databaseUri())) + } + + d.Set("state", db.State) + return nil +} + +func resourceSpannerDatabaseDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + id, err := buildSpannerDatabaseId(d, config) + if err != nil { + return err + } + + _, err = config.clientSpanner.Projects.Instances.Databases.DropDatabase( + id.databaseUri()).Do() + if err != nil { + return fmt.Errorf("Error, failed to delete Spanner Database %s: %s", id.databaseUri(), err) + } + + d.SetId("") + return nil +} + +func resourceSpannerDatabaseImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + id, err := importSpannerDatabaseId(d.Id()) + if err != nil { + return nil, err + } + + if id.Project != "" { + d.Set("project", id.Project) + } else { + project, err := getProject(d, config) + if err != nil { + return nil, err + } + id.Project = project + } + + d.Set("instance", id.Instance) + d.Set("name", id.Database) + d.SetId(id.terraformId()) + + return []*schema.ResourceData{d}, nil +} + +func buildSpannerDatabaseId(d *schema.ResourceData, config *Config) (*spannerDatabaseId, error) { + project, err := getProject(d, config) + if err != nil { + return nil, err + } + dbName := d.Get("name").(string) + instanceName := d.Get("instance").(string) + + return &spannerDatabaseId{ + Project: project, + Instance: instanceName, + Database: dbName, + }, nil +} + +type spannerDatabaseId struct { + Project string + Instance string + Database string +} + +func (s spannerDatabaseId) terraformId() string { + return fmt.Sprintf("%s/%s/%s", s.Project, s.Instance, s.Database) +} + +func (s spannerDatabaseId) parentProjectUri() string { + return fmt.Sprintf("projects/%s", s.Project) +} + +func (s spannerDatabaseId) parentInstanceUri() string { + return fmt.Sprintf("%s/instances/%s", s.parentProjectUri(), s.Instance) +} + +func (s spannerDatabaseId) databaseUri() string { + return fmt.Sprintf("%s/databases/%s", s.parentInstanceUri(), s.Database) +} + +func importSpannerDatabaseId(id string) (*spannerDatabaseId, error) { + if !regexp.MustCompile("^[a-z0-9-]+/[a-z0-9-]+$").Match([]byte(id)) && + !regexp.MustCompile("^[a-z0-9-]+/[a-z0-9-]+/[a-z0-9-]+$").Match([]byte(id)) { + return nil, fmt.Errorf("Invalid spanner database specifier. " + + "Expecting either {projectId}/{instanceId}/{dbId} OR " + + "{instanceId}/{dbId} (where project will be derived from the provider)") + } + + parts := strings.Split(id, "/") + if len(parts) == 2 { + log.Printf("[INFO] Spanner database import format of {instanceId}/{dbId} specified: %s", id) + return &spannerDatabaseId{Instance: parts[0], Database: parts[1]}, nil + } + + log.Printf("[INFO] Spanner database import format of {projectId}/{instanceId}/{dbId} specified: %s", id) + return extractSpannerDatabaseId(id) +} + +func extractSpannerDatabaseId(id string) (*spannerDatabaseId, error) { + if !regexp.MustCompile("^[a-z0-9-]+/[a-z0-9-]+/[a-z0-9-]+$").Match([]byte(id)) { + return nil, fmt.Errorf("Invalid spanner id format, expecting {projectId}/{instanceId}/{databaseId}") + } + parts := strings.Split(id, "/") + return &spannerDatabaseId{ + Project: parts[0], + Instance: parts[1], + Database: parts[2], + }, nil +} diff --git a/google/resource_spanner_database_test.go b/google/resource_spanner_database_test.go new file mode 100644 index 00000000000..0361f6a74be --- /dev/null +++ b/google/resource_spanner_database_test.go @@ -0,0 +1,321 @@ +package google + +import ( + "fmt" + "net/http" + "regexp" + "strings" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + + "google.golang.org/api/googleapi" + "google.golang.org/api/spanner/v1" +) + +// Unit Tests + +func TestDatabaseNameForApi(t *testing.T) { + id := spannerDatabaseId{ + Project: "project123", + Instance: "instance456", + Database: "db789", + } + actual := id.databaseUri() + expected := "projects/project123/instances/instance456/databases/db789" + expectEquals(t, expected, actual) +} + +func TestImportSpannerDatabaseId_InstanceDB(t *testing.T) { + id, e := importSpannerDatabaseId("instance456/database789") + if e != nil { + t.Errorf("Error should have been nil") + } + expectEquals(t, "", id.Project) + expectEquals(t, "instance456", id.Instance) + expectEquals(t, "database789", id.Database) +} + +func TestImportSpannerDatabaseId_ProjectInstanceDB(t *testing.T) { + id, e := importSpannerDatabaseId("project123/instance456/database789") + if e != nil { + t.Errorf("Error should have been nil") + } + expectEquals(t, "project123", id.Project) + expectEquals(t, "instance456", id.Instance) + expectEquals(t, "database789", id.Database) +} + +func TestImportSpannerDatabaseId_invalidLeadingSlash(t *testing.T) { + id, e := importSpannerDatabaseId("/instance456/database789") + expectInvalidSpannerDbImportId(t, id, e) +} + +func TestImportSpannerDatabaseId_invalidTrailingSlash(t *testing.T) { + id, e := importSpannerDatabaseId("instance456/database789/") + expectInvalidSpannerDbImportId(t, id, e) +} + +func TestImportSpannerDatabaseId_invalidSingleSlash(t *testing.T) { + id, e := importSpannerDatabaseId("/") + expectInvalidSpannerDbImportId(t, id, e) +} + +func TestImportSpannerDatabaseId_invalidMultiSlash(t *testing.T) { + id, e := importSpannerDatabaseId("project123/instance456/db789/next") + expectInvalidSpannerDbImportId(t, id, e) +} + +func expectInvalidSpannerDbImportId(t *testing.T, id *spannerDatabaseId, e error) { + if id != nil { + t.Errorf("Expected spannerDatabaseId to be nil") + return + } + if e == nil { + t.Errorf("Expected an Error but did not get one") + return + } + if !strings.HasPrefix(e.Error(), "Invalid spanner database specifier") { + t.Errorf("Expecting Error starting with 'Invalid spanner database specifier'") + } +} + +// Acceptance Tests + +func TestAccSpannerDatabase_basic(t *testing.T) { + var db spanner.Database + rnd := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccCheckSpannerInstanceDestroy, + testAccCheckSpannerDatabaseDestroy), + Steps: []resource.TestStep{ + { + Config: testAccSpannerDatabase_basic(rnd), + Check: resource.ComposeTestCheckFunc( + testAccCheckSpannerDatabaseExists("google_spanner_database.basic", &db), + + resource.TestCheckResourceAttr("google_spanner_database.basic", "name", "my-db-"+rnd), + resource.TestCheckResourceAttrSet("google_spanner_database.basic", "state"), + ), + }, + }, + }) +} + +func TestAccSpannerDatabase_basicWithInitialDDL(t *testing.T) { + var db spanner.Database + rnd := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccCheckSpannerInstanceDestroy, + testAccCheckSpannerDatabaseDestroy), + Steps: []resource.TestStep{ + { + Config: testAccSpannerDatabase_basicWithInitialDDL(rnd), + Check: resource.ComposeTestCheckFunc( + testAccCheckSpannerDatabaseExists("google_spanner_database.basic", &db), + ), + }, + }, + }) +} + +func TestAccSpannerDatabase_duplicateNameError(t *testing.T) { + var db spanner.Database + rnd := acctest.RandString(10) + dbName := fmt.Sprintf("spanner-test-%s", rnd) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: resource.ComposeTestCheckFunc( + testAccCheckSpannerInstanceDestroy, + testAccCheckSpannerDatabaseDestroy), + Steps: []resource.TestStep{ + { + Config: testAccSpannerDatabase_duplicateNameError_part1(rnd, dbName), + Check: resource.ComposeTestCheckFunc( + testAccCheckSpannerDatabaseExists("google_spanner_database.basic1", &db), + ), + }, + { + Config: testAccSpannerDatabase_duplicateNameError_part2(rnd, dbName), + ExpectError: regexp.MustCompile( + fmt.Sprintf(".*A database with name %s already exists", dbName)), + }, + }, + }) +} + +func testAccCheckSpannerDatabaseDestroy(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_spanner_database" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Unable to verify delete of spanner database, ID is empty") + } + + project, err := getTestProject(rs.Primary, config) + if err != nil { + return err + } + + id := spannerDatabaseId{ + Project: project, + Instance: rs.Primary.Attributes["instance"], + Database: rs.Primary.Attributes["name"], + } + _, err = config.clientSpanner.Projects.Instances.Databases.Get( + id.databaseUri()).Do() + + if err != nil { + if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound { + return nil + } + return fmt.Errorf("Error make GCP platform call to verify spanner database deleted: %s", err.Error()) + } + return fmt.Errorf("Spanner database not destroyed - still exists") + } + + return nil +} + +func testAccCheckSpannerDatabaseExists(n string, instance *spanner.Database) resource.TestCheckFunc { + return func(s *terraform.State) error { + config := testAccProvider.Meta().(*Config) + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Terraform resource Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set for Spanner instance") + } + + id, err := extractSpannerDatabaseId(rs.Primary.ID) + found, err := config.clientSpanner.Projects.Instances.Databases.Get( + id.databaseUri()).Do() + if err != nil { + return err + } + + fName := extractInstanceNameFromUri(found.Name) + if fName != id.Database { + return fmt.Errorf("Spanner database %s not found, found %s instead", id.Database, fName) + } + + *instance = *found + + return nil + } +} + +func testAccSpannerDatabase_basic(rnd string) string { + return fmt.Sprintf(` +resource "google_spanner_instance" "basic" { + name = "my-instance-%s" + config = "regional-us-central1" + display_name = "my-displayname-%s" + num_nodes = 1 +} + +resource "google_spanner_database" "basic" { + instance = "${google_spanner_instance.basic.name}" + name = "my-db-%s" + +} +`, rnd, rnd, rnd) +} + +func testAccSpannerDatabase_basicWithInitialDDL(rnd string) string { + return fmt.Sprintf(` +resource "google_spanner_instance" "basic" { + name = "my-instance-%s" + config = "regional-us-central1" + display_name = "my-displayname-%s" + num_nodes = 1 +} + +resource "google_spanner_database" "basic" { + instance = "${google_spanner_instance.basic.name}" + name = "my-db-%s" + ddl = [ + "CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)", + "CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)" ] +} +`, rnd, rnd, rnd) +} + +func testAccSpannerDatabase_duplicateNameError_part1(rnd, dbName string) string { + return fmt.Sprintf(` +resource "google_spanner_instance" "basic" { + name = "my-instance-%s" + config = "regional-us-central1" + display_name = "my-displayname-%s" + num_nodes = 1 +} + +resource "google_spanner_database" "basic1" { + instance = "${google_spanner_instance.basic.name}" + name = "%s" + +} +`, rnd, rnd, dbName) +} + +func testAccSpannerDatabase_duplicateNameError_part2(rnd, dbName string) string { + return fmt.Sprintf(` +%s + +resource "google_spanner_database" "basic2" { + instance = "${google_spanner_instance.basic.name}" + name = "%s" +} +`, testAccSpannerDatabase_duplicateNameError_part1(rnd, dbName), dbName) +} + +func testAccSpannerDatabase_basicImport(iname, dbname string) string { + return fmt.Sprintf(` +resource "google_spanner_instance" "basic" { + name = "%s" + config = "regional-us-central1" + display_name = "%s" + num_nodes = 1 +} + +resource "google_spanner_database" "basic" { + instance = "${google_spanner_instance.basic.name}" + name = "%s" + +} +`, iname, iname, dbname) +} + +func testAccSpannerDatabase_basicImportWithProject(project, iname, dbname string) string { + return fmt.Sprintf(` +resource "google_spanner_instance" "basic" { + project = "%s" + name = "%s" + config = "regional-us-central1" + display_name = "%s" + num_nodes = 1 +} + +resource "google_spanner_database" "basic" { + project = "%s" + instance = "${google_spanner_instance.basic.name}" + name = "%s" + +} +`, project, iname, iname, project, dbname) +} diff --git a/google/spanner_database_operation.go b/google/spanner_database_operation.go new file mode 100644 index 00000000000..eb18fe7a745 --- /dev/null +++ b/google/spanner_database_operation.go @@ -0,0 +1,62 @@ +package google + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "google.golang.org/api/spanner/v1" +) + +type SpannerDatabaseOperationWaiter struct { + Service *spanner.Service + Op *spanner.Operation +} + +func (w *SpannerDatabaseOperationWaiter) Conf() *resource.StateChangeConf { + return &resource.StateChangeConf{ + Pending: []string{"false"}, + Target: []string{"true"}, + Refresh: w.RefreshFunc(), + } +} + +func (w *SpannerDatabaseOperationWaiter) RefreshFunc() resource.StateRefreshFunc { + return func() (interface{}, string, error) { + + op, err := w.Service.Projects.Instances.Databases.Operations.Get(w.Op.Name).Do() + + if err != nil { + return nil, "", err + } + + log.Printf("[DEBUG] Got %v while polling for operation %s's 'done' status", op.Done, w.Op.Name) + + return op, fmt.Sprint(op.Done), nil + } +} + +func spannerDatabaseOperationWait(config *Config, op *spanner.Operation, activity string, timeoutMin int) error { + w := &SpannerDatabaseOperationWaiter{ + Service: config.clientSpanner, + Op: op, + } + + state := w.Conf() + state.Delay = 10 * time.Second + state.Timeout = time.Duration(timeoutMin) * time.Minute + state.MinTimeout = 2 * time.Second + opRaw, err := state.WaitForState() + if err != nil { + return fmt.Errorf("Error waiting for %s: %s", activity, err) + } + + op = opRaw.(*spanner.Operation) + if op.Error != nil { + return fmt.Errorf("Error code %v, message: %s", op.Error.Code, op.Error.Message) + } + + return nil + +} diff --git a/website/docs/r/spanner_database.html.markdown b/website/docs/r/spanner_database.html.markdown new file mode 100644 index 00000000000..2f42eedf8ab --- /dev/null +++ b/website/docs/r/spanner_database.html.markdown @@ -0,0 +1,71 @@ +--- +layout: "google" +page_title: "Google: google_spanner_database" +sidebar_current: "docs-google-spanner-database" +description: |- + Creates a Google Spanner Database within a Spanner Instance. +--- + +# google\_spanner\_instance + +Creates a Google Spanner Database within a Spanner Instance. For more information, see the [official documentation](https://cloud.google.com/spanner/), or the [JSON API](https://cloud.google.com/spanner/docs/reference/rest/v1/projects.instances.databases). + +## Example Usage + +Example creating a Spanner database. + +```hcl +resource "google_spanner_instance" "main" { + config = "regional-europe-west1" + display_name = "main-instance" +} + +resource "google_spanner_database" "db" { + instance = "${google_spanner_instance.main.name}" + name = "main-instance" + ddl = [ + "CREATE TABLE t1 (t1 INT64 NOT NULL,) PRIMARY KEY(t1)", + "CREATE TABLE t2 (t2 INT64 NOT NULL,) PRIMARY KEY(t2)" + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `instance` - (Required) The name of the instance that will serve the new database. + +* `name` - (Required) The name of the database. + +- - - + +* `project` - (Optional) The project in which to look for the `instance` specified. If it + is not provided, the provider project is used. + +* `ddl` - (Optional) An optional list of DDL statements to run inside the newly created + database. Statements can create tables, indexes, etc. These statements execute atomically + with the creation of the database: if there is an error in any statement, the database + is not created. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `state` - The current state of the database. + +## Import + +Databases can be imported via their `instance` and `name` values, and optionally +the `project` in which the instance is defined (Often used when the project is different +to that defined in the provider). The format is thus either `{instanceName}/{dbName}` or +`{projectId}/{instanceName}/{dbName}`. e.g. + +``` +$ terraform import google_spanner_database.db1 instance456/db789 + +$ terraform import google_spanner_database.db1 project123/instance456/db789 + +``` diff --git a/website/google.erb b/website/google.erb index c28e53c6820..4e29305663b 100644 --- a/website/google.erb +++ b/website/google.erb @@ -285,6 +285,10 @@ > google_spanner_instance + + > + google_spanner_database +