-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add support for Google Cloud Bigtable. #177
Changes from all commits
53b9163
2c26153
048bfe5
5bceaef
cfe9e4a
ec2f933
28b35fa
d0d116f
a358f41
e5ff2b7
fc24b32
c8c5f17
fa14884
124555b
e5c7382
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package google | ||
|
||
import ( | ||
"context" | ||
|
||
"cloud.google.com/go/bigtable" | ||
"golang.org/x/oauth2" | ||
"google.golang.org/api/option" | ||
) | ||
|
||
type BigtableClientFactory struct { | ||
UserAgent string | ||
TokenSource oauth2.TokenSource | ||
} | ||
|
||
func (s BigtableClientFactory) NewInstanceAdminClient(project string) (*bigtable.InstanceAdminClient, error) { | ||
return bigtable.NewInstanceAdminClient(context.Background(), project, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent)) | ||
} | ||
|
||
func (s BigtableClientFactory) NewAdminClient(project, instance string) (*bigtable.AdminClient, error) { | ||
return bigtable.NewAdminClient(context.Background(), project, instance, option.WithTokenSource(s.TokenSource), option.WithUserAgent(s.UserAgent)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package google | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/helper/validation" | ||
|
||
"cloud.google.com/go/bigtable" | ||
"golang.org/x/net/context" | ||
) | ||
|
||
func resourceBigtableInstance() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceBigtableInstanceCreate, | ||
Read: resourceBigtableInstanceRead, | ||
Delete: resourceBigtableInstanceDestroy, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"name": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"cluster_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"zone": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"display_name": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
Computed: true, | ||
}, | ||
|
||
"num_nodes": { | ||
Type: schema.TypeInt, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: 3, | ||
ValidateFunc: IntAtLeast(3), | ||
}, | ||
|
||
"storage_type": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: "SSD", | ||
ValidateFunc: validation.StringInSlice([]string{"SSD", "HDD"}, false), | ||
}, | ||
|
||
"project": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceBigtableInstanceCreate(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
ctx := context.Background() | ||
|
||
project, err := getProject(d, config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
name := d.Get("name").(string) | ||
displayName, ok := d.GetOk("display_name") | ||
if !ok { | ||
displayName = name | ||
} | ||
|
||
var storageType bigtable.StorageType | ||
switch value := d.Get("storage_type"); value { | ||
case "HDD": | ||
storageType = bigtable.HDD | ||
case "SSD": | ||
storageType = bigtable.SSD | ||
} | ||
|
||
instanceConf := &bigtable.InstanceConf{ | ||
InstanceId: name, | ||
DisplayName: displayName.(string), | ||
ClusterId: d.Get("cluster_id").(string), | ||
NumNodes: int32(d.Get("num_nodes").(int)), | ||
StorageType: storageType, | ||
Zone: d.Get("zone").(string), | ||
} | ||
|
||
c, err := config.bigtableClientFactory.NewInstanceAdminClient(project) | ||
if err != nil { | ||
return fmt.Errorf("Error starting instance admin client. %s", err) | ||
} | ||
|
||
defer c.Close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think golang tooling gets mad if you don't check the return result here in case it returns an error There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't had Gogland get mad at me, and I don't think we can do anything useful with an error from this - any interesting errors would have come from operations we performed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah agree, the only thing we can do is log it but if we're closing it I doubt we're interested in the log message 👍 |
||
|
||
err = c.CreateInstance(ctx, instanceConf) | ||
if err != nil { | ||
return fmt.Errorf("Error creating instance. %s", err) | ||
} | ||
|
||
d.SetId(name) | ||
|
||
return resourceBigtableInstanceRead(d, meta) | ||
} | ||
|
||
func resourceBigtableInstanceRead(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
ctx := context.Background() | ||
|
||
project, err := getProject(d, config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c, err := config.bigtableClientFactory.NewInstanceAdminClient(project) | ||
if err != nil { | ||
return fmt.Errorf("Error starting instance admin client. %s", err) | ||
} | ||
|
||
defer c.Close() | ||
|
||
instance, err := c.InstanceInfo(ctx, d.Id()) | ||
if err != nil { | ||
log.Printf("[WARN] Removing %s because it's gone", d.Id()) | ||
d.SetId("") | ||
return fmt.Errorf("Error retrieving instance. Could not find %s. %s", d.Id(), err) | ||
} | ||
|
||
d.Set("name", instance.Name) | ||
d.Set("display_name", instance.DisplayName) | ||
|
||
return nil | ||
} | ||
|
||
func resourceBigtableInstanceDestroy(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
ctx := context.Background() | ||
|
||
project, err := getProject(d, config) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c, err := config.bigtableClientFactory.NewInstanceAdminClient(project) | ||
if err != nil { | ||
return fmt.Errorf("Error starting instance admin client. %s", err) | ||
} | ||
|
||
defer c.Close() | ||
|
||
name := d.Id() | ||
err = c.DeleteInstance(ctx, name) | ||
if err != nil { | ||
return fmt.Errorf("Error deleting instance. %s", err) | ||
} | ||
|
||
d.SetId("") | ||
|
||
return nil | ||
} | ||
|
||
// IntAtLeast returns a SchemaValidateFunc which tests if the provided value | ||
// is of type int and is above min (inclusive) | ||
func IntAtLeast(min int) schema.SchemaValidateFunc { | ||
return func(i interface{}, k string) (s []string, es []error) { | ||
v, ok := i.(int) | ||
if !ok { | ||
es = append(es, fmt.Errorf("expected type of %s to be int", k)) | ||
return | ||
} | ||
|
||
if v < min { | ||
es = append(es, fmt.Errorf("expected %s to be at least %d, got %d", k, min, v)) | ||
return | ||
} | ||
|
||
return | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package google | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform/helper/acctest" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
) | ||
|
||
func TestAccBigtableInstance_basic(t *testing.T) { | ||
instanceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckBigtableInstanceDestroy, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccBigtableInstance(instanceName), | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccBigtableInstanceExists( | ||
"google_bigtable_instance.instance"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckBigtableInstanceDestroy(s *terraform.State) error { | ||
var ctx = context.Background() | ||
for _, rs := range s.RootModule().Resources { | ||
if rs.Type != "google_bigtable_instance" { | ||
continue | ||
} | ||
|
||
config := testAccProvider.Meta().(*Config) | ||
c, err := config.bigtableClientFactory.NewInstanceAdminClient(config.Project) | ||
if err != nil { | ||
return fmt.Errorf("Error starting instance admin client. %s", err) | ||
} | ||
|
||
_, err = c.InstanceInfo(ctx, rs.Primary.Attributes["name"]) | ||
if err == nil { | ||
return fmt.Errorf("Instance %s still exists.", rs.Primary.Attributes["name"]) | ||
} | ||
|
||
c.Close() | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func testAccBigtableInstanceExists(n string) resource.TestCheckFunc { | ||
var ctx = context.Background() | ||
return func(s *terraform.State) error { | ||
rs, ok := s.RootModule().Resources[n] | ||
if !ok { | ||
return fmt.Errorf("Not found: %s", n) | ||
} | ||
|
||
if rs.Primary.ID == "" { | ||
return fmt.Errorf("No ID is set") | ||
} | ||
config := testAccProvider.Meta().(*Config) | ||
c, err := config.bigtableClientFactory.NewInstanceAdminClient(config.Project) | ||
if err != nil { | ||
return fmt.Errorf("Error starting instance admin client. %s", err) | ||
} | ||
|
||
_, err = c.InstanceInfo(ctx, rs.Primary.Attributes["name"]) | ||
if err != nil { | ||
return fmt.Errorf("Error retrieving instance %s.", rs.Primary.Attributes["name"]) | ||
} | ||
|
||
c.Close() | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func testAccBigtableInstance(instanceName string) string { | ||
return fmt.Sprintf(` | ||
resource "google_bigtable_instance" "instance" { | ||
name = "%s" | ||
cluster_id = "%s" | ||
zone = "us-central1-b" | ||
num_nodes = 3 | ||
storage_type = "HDD" | ||
} | ||
`, instanceName, instanceName) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This value can't be updated? Seems like a natural thing to want to adjust later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't update it with the client as-is, or even read it for that matter. When the client supports updating this value, we can remove
ForceNew
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK