2017-08-14 16:53:11 +00:00
|
|
|
package google
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
2018-12-14 01:51:11 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/helper/schema"
|
2017-08-14 16:53:11 +00:00
|
|
|
|
|
|
|
"google.golang.org/api/googleapi"
|
|
|
|
"google.golang.org/api/spanner/v1"
|
|
|
|
)
|
|
|
|
|
2018-10-25 21:29:03 +00:00
|
|
|
const (
|
|
|
|
spannerDatabaseNameFormat = "^[a-z][a-z0-9_-]*[a-z0-9]$"
|
|
|
|
)
|
|
|
|
|
2017-08-14 16:53:11 +00:00
|
|
|
func resourceSpannerDatabase() *schema.Resource {
|
|
|
|
return &schema.Resource{
|
|
|
|
Create: resourceSpannerDatabaseCreate,
|
|
|
|
Read: resourceSpannerDatabaseRead,
|
|
|
|
Delete: resourceSpannerDatabaseDelete,
|
|
|
|
Importer: &schema.ResourceImporter{
|
2018-10-25 21:29:03 +00:00
|
|
|
State: resourceSpannerDatabaseImport,
|
2017-08-14 16:53:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
|
|
"instance": &schema.Schema{
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
},
|
|
|
|
|
|
|
|
"name": &schema.Schema{
|
2018-10-25 21:29:03 +00:00
|
|
|
Type: schema.TypeString,
|
|
|
|
Required: true,
|
|
|
|
ForceNew: true,
|
|
|
|
ValidateFunc: validateResourceSpannerDatabaseName,
|
2017-08-14 16:53:11 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
"project": {
|
|
|
|
Type: schema.TypeString,
|
|
|
|
Optional: true,
|
2017-11-28 00:32:20 +00:00
|
|
|
Computed: true,
|
2017-08-14 16:53:11 +00:00
|
|
|
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)
|
2017-11-28 00:32:20 +00:00
|
|
|
d.Set("project", id.Project)
|
2017-08-14 16:53:11 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-10-25 21:29:03 +00:00
|
|
|
func resourceSpannerDatabaseImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
2018-12-14 01:51:11 +00:00
|
|
|
config := meta.(*Config)
|
2018-10-25 21:29:03 +00:00
|
|
|
err := parseImportId([]string{
|
|
|
|
"projects/(?P<project>[^/]+)/instances/(?P<instance>[^/]+)/databases/(?P<name>[^/]+)",
|
|
|
|
"instances/(?P<instance>[^/]+)/databases/(?P<name>[^/]+)",
|
|
|
|
"(?P<project>[^/]+)/(?P<instance>[^/]+)/(?P<name>[^/]+)",
|
|
|
|
"(?P<instance>[^/]+)/(?P<name>[^/]+)",
|
|
|
|
}, d, config)
|
2018-12-14 01:51:11 +00:00
|
|
|
if err != nil {
|
2018-10-25 21:29:03 +00:00
|
|
|
return nil, fmt.Errorf("Error constructing id: %s", err)
|
2018-12-14 01:51:11 +00:00
|
|
|
}
|
2017-08-14 16:53:11 +00:00
|
|
|
|
2018-10-25 21:29:03 +00:00
|
|
|
id, err := buildSpannerDatabaseId(d, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error constructing id: %s", err)
|
2018-12-14 01:51:11 +00:00
|
|
|
}
|
2017-08-14 16:53:11 +00:00
|
|
|
|
2018-12-14 01:51:11 +00:00
|
|
|
d.SetId(id.terraformId())
|
2017-08-14 16:53:11 +00:00
|
|
|
|
2018-12-14 01:51:11 +00:00
|
|
|
return []*schema.ResourceData{d}, nil
|
2017-08-14 16:53:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func buildSpannerDatabaseId(d *schema.ResourceData, config *Config) (*spannerDatabaseId, error) {
|
|
|
|
project, err := getProject(d, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-12-14 01:51:11 +00:00
|
|
|
dbName := d.Get("name").(string)
|
2017-08-14 16:53:11 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2018-12-14 01:51:11 +00:00
|
|
|
func extractSpannerDatabaseId(id string) (*spannerDatabaseId, error) {
|
2018-10-25 21:29:03 +00:00
|
|
|
if !regexp.MustCompile(fmt.Sprintf("^%s/[a-z0-9-]+/%s$", ProjectRegex, spannerDatabaseNameFormat)).Match([]byte(id)) {
|
2018-12-14 01:51:11 +00:00
|
|
|
return nil, fmt.Errorf("Invalid spanner id format, expecting {projectId}/{instanceId}/{databaseId}")
|
2018-10-19 22:43:14 +00:00
|
|
|
}
|
2018-12-14 01:51:11 +00:00
|
|
|
parts := strings.Split(id, "/")
|
|
|
|
return &spannerDatabaseId{
|
|
|
|
Project: parts[0],
|
|
|
|
Instance: parts[1],
|
|
|
|
Database: parts[2],
|
|
|
|
}, nil
|
2018-10-19 22:43:14 +00:00
|
|
|
}
|
2018-10-25 21:29:03 +00:00
|
|
|
|
|
|
|
func validateResourceSpannerDatabaseName(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(spannerDatabaseNameFormat).MatchString(value) {
|
|
|
|
errors = append(errors, fmt.Errorf("database name %q must match regexp %q", value, spannerDatabaseNameFormat))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|