Skip to content

Commit

Permalink
Merge pull request #177 from terraform-providers/bigtable-support
Browse files Browse the repository at this point in the history
Add support for Google Cloud Bigtable.
  • Loading branch information
selmanj authored Jul 11, 2017
2 parents e5d55df + e5c7382 commit 95e72d5
Show file tree
Hide file tree
Showing 14 changed files with 765 additions and 28 deletions.
22 changes: 22 additions & 0 deletions google/bigtable_client_factory.go
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))
}
20 changes: 18 additions & 2 deletions google/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package google

import (
"context"
"encoding/json"
"fmt"
"log"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform"

"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
Expand Down Expand Up @@ -45,6 +47,8 @@ type Config struct {
clientIAM *iam.Service
clientServiceMan *servicemanagement.APIService
clientBigQuery *bigquery.Service

bigtableClientFactory *BigtableClientFactory
}

func (c *Config) loadAndValidate() error {
Expand All @@ -57,6 +61,7 @@ func (c *Config) loadAndValidate() error {
}

var client *http.Client
var tokenSource oauth2.TokenSource

if c.Credentials != "" {
contents, _, err := pathorcontents.Read(c.Credentials)
Expand Down Expand Up @@ -85,12 +90,18 @@ func (c *Config) loadAndValidate() error {
// Initiate an http.Client. The following GET request will be
// authorized and authenticated on the behalf of
// your service account.
client = conf.Client(oauth2.NoContext)
client = conf.Client(context.Background())

tokenSource = conf.TokenSource(context.Background())
} else {
log.Printf("[INFO] Authenticating using DefaultClient")
err := error(nil)
client, err = google.DefaultClient(oauth2.NoContext, clientScopes...)
client, err = google.DefaultClient(context.Background(), clientScopes...)
if err != nil {
return err
}

tokenSource, err = google.DefaultTokenSource(context.Background(), clientScopes...)
if err != nil {
return err
}
Expand Down Expand Up @@ -181,6 +192,11 @@ func (c *Config) loadAndValidate() error {
}
c.clientBigQuery.UserAgent = userAgent

c.bigtableClientFactory = &BigtableClientFactory{
UserAgent: userAgent,
TokenSource: tokenSource,
}

return nil
}

Expand Down
2 changes: 2 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"google_bigquery_dataset": resourceBigQueryDataset(),
"google_bigquery_table": resourceBigQueryTable(),
"google_bigtable_instance": resourceBigtableInstance(),
"google_bigtable_table": resourceBigtableTable(),
"google_compute_autoscaler": resourceComputeAutoscaler(),
"google_compute_address": resourceComputeAddress(),
"google_compute_backend_bucket": resourceComputeBackendBucket(),
Expand Down
193 changes: 193 additions & 0 deletions google/resource_bigtable_instance.go
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()

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
}
}
94 changes: 94 additions & 0 deletions google/resource_bigtable_instance_test.go
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)
}
Loading

0 comments on commit 95e72d5

Please sign in to comment.