Skip to content

Commit

Permalink
Cloud functions (#899)
Browse files Browse the repository at this point in the history
* Initial commit

* Adding google_cloudfunction_function resource

* Some FMT updates

* Working Cloud Function Create/Delete/Get
Create is limited to gs:// source now.

* Fixed tests import

* Terraform now is able to apply and destroy function

* Fully working Basic test

* Added:
1. Allowed region check
2. readTimeout helper

* Found better solution for conflicting values

* Adding description

* Adding full basic test

* dded Update functionality

* Made few more optional params

* Added test for Labels

* Added update tests

* Added storage_* members and made function source deploy from storage bucket object

* Adding comments

* Adding tests for PubSub

* Adding tests for Bucket

* Adding Data provider

* Fixing bug which allowed to miss error

* Amending Operation retrieval

* Fixing vet errors and vendoring cloudfunctions/v1

* Fixing according to comments

* Fixing according to comments round #2

* Fixing tabs to space

* Fixing tabs to space and some comments #3

* Re-done update to include labels in one update with others

* Adding back default values. In case of such scenario, when user creates function with some values for "timeout" or "available_memory_mb", and then disables those attributes. Terraform plan then gives:
No changes. Infrastructure is up-to-date.
This is an error. By adding const we would avoid this error.

* Fixed MixedCase and more tabs
  • Loading branch information
sarunask authored and danawillow committed Jan 10, 2018
1 parent 03cec5e commit f07c332
Show file tree
Hide file tree
Showing 13 changed files with 4,990 additions and 0 deletions.
70 changes: 70 additions & 0 deletions google/cloudfunctions_operation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package google

import (
"fmt"
"log"
"time"

"github.com/hashicorp/terraform/helper/resource"
"google.golang.org/api/cloudfunctions/v1"
)

type CloudFunctionsOperationWaiter struct {
Service *cloudfunctions.Service
Op *cloudfunctions.Operation
}

func (w *CloudFunctionsOperationWaiter) RefreshFunc() resource.StateRefreshFunc {
return func() (interface{}, string, error) {
op, err := w.Service.Operations.Get(w.Op.Name).Do()

if err != nil {
return nil, "", err
}

status := "PENDING"
if op.Done == true {
status = "DONE"
}

log.Printf("[DEBUG] Got %q when asking for operation %q", status, w.Op.Name)
return op, status, nil
}
}

func (w *CloudFunctionsOperationWaiter) Conf() *resource.StateChangeConf {
return &resource.StateChangeConf{
Pending: []string{"PENDING"},
Target: []string{"DONE"},
Refresh: w.RefreshFunc(),
}
}

func cloudFunctionsOperationWait(client *cloudfunctions.Service,
op *cloudfunctions.Operation, activity string) error {
return cloudFunctionsOperationWaitTime(client, op, activity, 4)
}

func cloudFunctionsOperationWaitTime(client *cloudfunctions.Service, op *cloudfunctions.Operation,
activity string, timeoutMin int) error {
w := &CloudFunctionsOperationWaiter{
Service: client,
Op: op,
}

state := w.Conf()
state.Delay = 10 * time.Second
state.Timeout = time.Duration(timeoutMin) * time.Minute
state.MinTimeout = 2 * time.Second
opRaw, err := state.WaitForState()
if err != nil {
return fmt.Errorf("Error waiting for %s: %s", activity, err)
}

resultOp := opRaw.(*cloudfunctions.Operation)
if resultOp.Error != nil {
return fmt.Errorf(resultOp.Error.Message)
}

return nil
}
9 changes: 9 additions & 0 deletions google/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"golang.org/x/oauth2/jwt"
"google.golang.org/api/bigquery/v2"
"google.golang.org/api/cloudbilling/v1"
"google.golang.org/api/cloudfunctions/v1"
"google.golang.org/api/cloudkms/v1"
"google.golang.org/api/cloudresourcemanager/v1"
resourceManagerV2Beta1 "google.golang.org/api/cloudresourcemanager/v2beta1"
Expand Down Expand Up @@ -64,6 +65,7 @@ type Config struct {
clientIAM *iam.Service
clientServiceMan *servicemanagement.APIService
clientBigQuery *bigquery.Service
clientCloudFunctions *cloudfunctions.Service

bigtableClientFactory *BigtableClientFactory
}
Expand Down Expand Up @@ -244,6 +246,13 @@ func (c *Config) loadAndValidate() error {
}
c.clientBigQuery.UserAgent = userAgent

log.Printf("[INFO] Instantiating Google Cloud CloudFunctions Client...")
c.clientCloudFunctions, err = cloudfunctions.New(client)
if err != nil {
return err
}
c.clientCloudFunctions.UserAgent = userAgent

c.bigtableClientFactory = &BigtableClientFactory{
UserAgent: userAgent,
TokenSource: tokenSource,
Expand Down
45 changes: 45 additions & 0 deletions google/data_source_google_cloudfunctions_function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package google

import (
"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceGoogleCloudFunctionsFunction() *schema.Resource {
// Generate datasource schema from resource
dsSchema := datasourceSchemaFromResourceSchema(resourceCloudFunctionsFunction().Schema)

// Set 'Required' schema elements
addRequiredFieldsToSchema(dsSchema, "name")

// Set 'Optional' schema elements
addOptionalFieldsToSchema(dsSchema, "project", "region")

return &schema.Resource{
Read: dataSourceGoogleCloudFunctionsFunctionRead,
Schema: dsSchema,
}
}

func dataSourceGoogleCloudFunctionsFunctionRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

region, err := getRegion(d, config)
if err != nil {
return err
}

cloudFuncId := &cloudFunctionId{
Project: project,
Region: region,
Name: d.Get("name").(string),
}

d.SetId(cloudFuncId.terraformId())

return resourceCloudFunctionsRead(d, meta)
}
154 changes: 154 additions & 0 deletions google/data_source_google_cloudfunctions_function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package google

import (
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccDataSourceGoogleCloudFunctionsFunction_basic(t *testing.T) {
t.Parallel()

funcDataNameHttp := "data.google_cloudfunctions_function.function_http"
funcDataNamePubSub := "data.google_cloudfunctions_function.function_pubsub"
funcDataNameBucket := "data.google_cloudfunctions_function.function_bucket"
functionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
bucketName := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt())
topicName := fmt.Sprintf("tf-test-sub-%s", acctest.RandString(10))
zipFilePath, err := createZIPArchiveForIndexJs(testHTTPTriggerPath)
if err != nil {
t.Fatal(err.Error())
}
defer os.Remove(zipFilePath) // clean up

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFunctionsFunctionDestroy,
Steps: []resource.TestStep{
{
Config: testAccDataSourceGoogleCloudFunctionsFunctionConfig(functionName,
bucketName, zipFilePath, topicName),
Check: resource.ComposeTestCheckFunc(
testAccDataSourceGoogleCloudFunctionsFunctionCheck(funcDataNameHttp,
"google_cloudfunctions_function.function_http"),
testAccDataSourceGoogleCloudFunctionsFunctionCheck(funcDataNamePubSub,
"google_cloudfunctions_function.function_pubsub"),
testAccDataSourceGoogleCloudFunctionsFunctionCheck(funcDataNameBucket,
"google_cloudfunctions_function.function_bucket"),
),
},
},
})
}

func testAccDataSourceGoogleCloudFunctionsFunctionCheck(dataSourceName string, resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
ds, ok := s.RootModule().Resources[dataSourceName]
if !ok {
return fmt.Errorf("root module has no resource called %s", dataSourceName)
}

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("can't find %s in state", resourceName)
}

dsAttr := ds.Primary.Attributes
rsAttr := rs.Primary.Attributes

cloudFuncAttrToCheck := []string{
"name",
"region",
"description",
"available_memory_mb",
"timeout",
"storage_bucket",
"storage_object",
"entry_point",
"trigger_http",
"trigger_bucket",
"trigger_topic",
}

for _, attr := range cloudFuncAttrToCheck {
if dsAttr[attr] != rsAttr[attr] {
return fmt.Errorf(
"%s is %s; want %s",
attr,
dsAttr[attr],
rsAttr[attr],
)
}
}

return nil
}
}

func testAccDataSourceGoogleCloudFunctionsFunctionConfig(functionName string,
bucketName string, zipFilePath string, topicName string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
}
resource "google_storage_bucket_object" "archive" {
name = "index.zip"
bucket = "${google_storage_bucket.bucket.name}"
source = "%s"
}
resource "google_cloudfunctions_function" "function_http" {
name = "%s-http"
description = "test function"
available_memory_mb = 128
source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_http = true
timeout = 61
entry_point = "helloGET"
}
resource "google_cloudfunctions_function" "function_bucket" {
name = "%s-bucket"
available_memory_mb = 128
source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_bucket = "${google_storage_bucket.bucket.name}"
timeout = 61
entry_point = "helloGET"
}
resource "google_pubsub_topic" "sub" {
name = "%s"
}
resource "google_cloudfunctions_function" "function_pubsub" {
name = "%s-pubsub"
available_memory_mb = 128
source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_topic = "${google_pubsub_topic.sub.name}"
timeout = 61
entry_point = "helloGET"
}
data "google_cloudfunctions_function" "function_http" {
name = "${google_cloudfunctions_function.function_http.name}"
}
data "google_cloudfunctions_function" "function_bucket" {
name = "${google_cloudfunctions_function.function_bucket.name}"
}
data "google_cloudfunctions_function" "function_pubsub" {
name = "${google_cloudfunctions_function.function_pubsub.name}"
}
`, bucketName, zipFilePath, functionName, functionName,
topicName, functionName)
}
2 changes: 2 additions & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func Provider() terraform.ResourceProvider {
"google_billing_account": dataSourceGoogleBillingAccount(),
"google_dns_managed_zone": dataSourceDnsManagedZone(),
"google_client_config": dataSourceGoogleClientConfig(),
"google_cloudfunctions_function": dataSourceGoogleCloudFunctionsFunction(),
"google_compute_address": dataSourceGoogleComputeAddress(),
"google_compute_image": dataSourceGoogleComputeImage(),
"google_compute_global_address": dataSourceGoogleComputeGlobalAddress(),
Expand All @@ -86,6 +87,7 @@ func Provider() terraform.ResourceProvider {
"google_bigquery_table": resourceBigQueryTable(),
"google_bigtable_instance": resourceBigtableInstance(),
"google_bigtable_table": resourceBigtableTable(),
"google_cloudfunctions_function": resourceCloudFunctionsFunction(),
"google_compute_autoscaler": resourceComputeAutoscaler(),
"google_compute_address": resourceComputeAddress(),
"google_compute_backend_bucket": resourceComputeBackendBucket(),
Expand Down
Loading

0 comments on commit f07c332

Please sign in to comment.