Skip to content
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

Merged
merged 15 commits into from
Jul 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

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

Copy link
Collaborator Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

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()
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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
}
}
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