diff --git a/google/import_spanner_database_test.go b/google/import_spanner_database_test.go index c5469150174..000a938959b 100644 --- a/google/import_spanner_database_test.go +++ b/google/import_spanner_database_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" ) -func TestAccGoogleSpannerDatabase_importBasic(t *testing.T) { +func TestAccGoogleSpannerDatabase_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)) @@ -23,16 +23,16 @@ func TestAccGoogleSpannerDatabase_importBasic(t *testing.T) { }, resource.TestStep{ - ResourceName: resourceName, - ImportStateIdPrefix: instanceName + "/", - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportStateId: instanceName + "/" + dbName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func TestAccGoogleSpannerDatabase_importWithProject(t *testing.T) { +func TestAccGoogleSpannerDatabase_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)) @@ -48,10 +48,9 @@ func TestAccGoogleSpannerDatabase_importWithProject(t *testing.T) { }, resource.TestStep{ - ResourceName: resourceName, - ImportStateIdPrefix: projectId + "/" + instanceName + "/", - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) diff --git a/google/resource_spanner_database.go b/google/resource_spanner_database.go index 703b9293ddb..727d7087cb2 100644 --- a/google/resource_spanner_database.go +++ b/google/resource_spanner_database.go @@ -75,38 +75,31 @@ func resourceSpannerDatabase() *schema.Resource { func resourceSpannerDatabaseCreate(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - timeoutMins := int(d.Timeout(schema.TimeoutCreate).Minutes()) - project, err := getProject(d, config) + id, err := buildSpannerDatabaseId(d, config) if err != nil { return err } - instanceName := d.Get("instance").(string) - dbName := d.Get("name").(string) - cdr := &spanner.CreateDatabaseRequest{} - cdr.CreateStatement = fmt.Sprintf("CREATE DATABASE `%s`", dbName) - + cdr.CreateStatement = fmt.Sprintf("CREATE DATABASE `%s`", id.Database) if v, ok := d.GetOk("ddl"); ok { - ddlList := v.([]interface{}) - ddls := []string{} - for _, v := range ddlList { - ddls = append(ddls, v.(string)) - } - cdr.ExtraStatements = ddls + cdr.ExtraStatements = convertStringArr(v.([]interface{})) } op, err := config.clientSpanner.Projects.Instances.Databases.Create( - instanceNameForApi(project, instanceName), cdr).Do() + 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", dbName) + 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", dbName, err) + 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 @@ -114,100 +107,133 @@ func resourceSpannerDatabaseCreate(d *schema.ResourceData, meta interface{}) err return waitErr } - log.Printf("[INFO] Spanner database %s has been created", dbName) - d.SetId(dbName) - + 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) - project, err := getProject(d, config) + id, err := buildSpannerDatabaseId(d, config) if err != nil { return err } - dbName := d.Get("name").(string) - instanceName := d.Get("instance").(string) - _, err = config.clientSpanner.Projects.Instances.Databases.Get( - databaseNameForApi(project, instanceName, dbName)).Do() + _, err = config.clientSpanner.Projects.Instances.Databases.Get( + id.databaseUri()).Do() if err != nil { - return handleNotFoundError(err, d, fmt.Sprintf("Spanner database %q", dbName)) + return handleNotFoundError(err, d, fmt.Sprintf("Spanner database %q", id.databaseUri())) } + d.SetId(id.terraformId()) return nil } func resourceSpannerDatabaseDelete(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) - project, err := getProject(d, config) + id, err := buildSpannerDatabaseId(d, config) if err != nil { return err } - dbName := d.Get("name").(string) - instanceName := d.Get("instance").(string) _, err = config.clientSpanner.Projects.Instances.Databases.DropDatabase( - databaseNameForApi(project, instanceName, dbName)).Do() + id.databaseUri()).Do() if err != nil { - return fmt.Errorf("Error, failed to delete Spanner Database %s: %s", dbName, err) + return fmt.Errorf("Error, failed to delete Spanner Database %s: %s", id.databaseUri(), err) } d.SetId("") return nil } -func databaseNameForApi(p, i, d string) string { - return instanceNameForApi(p, i) + "/databases/" + d -} - func resourceSpannerDatabaseImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - importId, err := extractSpannerDatabaseImportIds(d.Id()) + config := meta.(*Config) + id, err := importSpannerDatabaseId(d.Id()) if err != nil { return nil, err } - if importId.Project != "" { - d.Set("project", importId.Project) + 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", importId.Instance) - d.Set("name", importId.Database) - d.SetId(importId.Database) + + d.Set("instance", id.Instance) + d.Set("name", id.Database) + d.SetId(id.terraformId()) return []*schema.ResourceData{d}, nil } -type spannerDbImportId struct { +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 extractSpannerDatabaseImportIds(id string) (*spannerDbImportId, error) { - parts := strings.Split(id, "/") - if id == "" || strings.HasPrefix(id, "/") || strings.HasSuffix(id, "/") || - (len(parts) != 2 && len(parts) != 3) { +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)") } - sid := &spannerDbImportId{} - + parts := strings.Split(id, "/") if len(parts) == 2 { - log.Printf("[INFO] Spanner instance import format of {instanceId}/{dbId} specified: %s", id) - sid.Instance = parts[0] - sid.Database = parts[1] - } - if len(parts) == 3 { - log.Printf("[INFO] Spanner instance import format of {projectId}/{instanceId}/{dbId} specified: %s", id) - sid.Project = parts[0] - sid.Instance = parts[1] - sid.Database = parts[2] + log.Printf("[INFO] Spanner database import format of {instanceId}/{dbId} specified: %s", id) + return &spannerDatabaseId{Instance: parts[0], Database: parts[1]}, nil } - return sid, 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 index d1cadaa231e..7c6c5e6e89e 100644 --- a/google/resource_spanner_database_test.go +++ b/google/resource_spanner_database_test.go @@ -18,54 +18,59 @@ import ( // Unit Tests func TestDatabaseNameForApi(t *testing.T) { - actual := databaseNameForApi("project123", "instance456", "db789") + id := spannerDatabaseId{ + Project: "project123", + Instance: "instance456", + Database: "db789", + } + actual := id.databaseUri() expected := "projects/project123/instances/instance456/databases/db789" expectEquals(t, expected, actual) } -func TestExtractSpannerDBImportIds(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("instance456/database789") +func TestImportSpannerDatabaseId_InstanceDB(t *testing.T) { + id, e := importSpannerDatabaseId("instance456/database789") if e != nil { t.Errorf("Error should have been nil") } - expectEquals(t, "", sid.Project) - expectEquals(t, "instance456", sid.Instance) - expectEquals(t, "database789", sid.Database) + expectEquals(t, "", id.Project) + expectEquals(t, "instance456", id.Instance) + expectEquals(t, "database789", id.Database) } -func TestExtractSpannerDBImportIds_withProject(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("project123/instance456/database789") +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", sid.Project) - expectEquals(t, "instance456", sid.Instance) - expectEquals(t, "database789", sid.Database) + expectEquals(t, "project123", id.Project) + expectEquals(t, "instance456", id.Instance) + expectEquals(t, "database789", id.Database) } -func TestExtractSpannerDBImportIds_invalidLeadingSlash(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("/instance456/database789") - expectInvalidSpannerDbImportId(t, sid, e) +func TestImportSpannerDatabaseId_invalidLeadingSlash(t *testing.T) { + id, e := importSpannerDatabaseId("/instance456/database789") + expectInvalidSpannerDbImportId(t, id, e) } -func TestExtractSpannerDBImportIds_invalidTrailingSlash(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("instance456/database789/") - expectInvalidSpannerDbImportId(t, sid, e) +func TestImportSpannerDatabaseId_invalidTrailingSlash(t *testing.T) { + id, e := importSpannerDatabaseId("instance456/database789/") + expectInvalidSpannerDbImportId(t, id, e) } -func TestExtractSpannerDBImportIds_invalidSingleSlash(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("/") - expectInvalidSpannerDbImportId(t, sid, e) +func TestImportSpannerDatabaseId_invalidSingleSlash(t *testing.T) { + id, e := importSpannerDatabaseId("/") + expectInvalidSpannerDbImportId(t, id, e) } -func TestExtractSpannerDBImportIds_invalidMultiSlash(t *testing.T) { - sid, e := extractSpannerDatabaseImportIds("project123/instance456/db789/next") - expectInvalidSpannerDbImportId(t, sid, e) +func TestImportSpannerDatabaseId_invalidMultiSlash(t *testing.T) { + id, e := importSpannerDatabaseId("project123/instance456/db789/next") + expectInvalidSpannerDbImportId(t, id, e) } -func expectInvalidSpannerDbImportId(t *testing.T, sid *spannerDbImportId, e error) { - if sid != nil { - t.Errorf("Expected spannerDbImportId to be nil") +func expectInvalidSpannerDbImportId(t *testing.T, id *spannerDatabaseId, e error) { + if id != nil { + t.Errorf("Expected spannerDatabaseId to be nil") return } if e == nil { @@ -159,15 +164,18 @@ func testAccCheckSpannerDatabaseDestroy(s *terraform.State) error { return fmt.Errorf("Unable to verify delete of spanner database, ID is empty") } - project, err := getProjectId(rs, config) + project, err := getTestProject(rs.Primary, config) if err != nil { return err } - dbName := rs.Primary.Attributes["name"] - instanceName := rs.Primary.Attributes["instance"] + id := spannerDatabaseId{ + Project: project, + Instance: rs.Primary.Attributes["instance"], + Database: rs.Primary.Attributes["name"], + } _, err = config.clientSpanner.Projects.Instances.Databases.Get( - databaseNameForApi(project, instanceName, dbName)).Do() + id.databaseUri()).Do() if err != nil { if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound { @@ -193,22 +201,16 @@ func testAccCheckSpannerDatabaseExists(n string, instance *spanner.Database) res return fmt.Errorf("No ID is set for Spanner instance") } - project, err := getProjectId(rs, config) - if err != nil { - return err - } - - dbName := rs.Primary.Attributes["name"] - instanceName := rs.Primary.Attributes["instance"] + id, err := extractSpannerDatabaseId(rs.Primary.ID) found, err := config.clientSpanner.Projects.Instances.Databases.Get( - databaseNameForApi(project, instanceName, dbName)).Do() + id.databaseUri()).Do() if err != nil { return err } fName := extractInstanceNameFromApi(found.Name) - if fName != rs.Primary.ID { - return fmt.Errorf("Spanner database %s not found, found %s instead", rs.Primary.ID, fName) + if fName != id.Database { + return fmt.Errorf("Spanner database %s not found, found %s instead", id.Database, fName) } *instance = *found