From 7dd80d71024c40158ebba572fa462d12a97a50f1 Mon Sep 17 00:00:00 2001 From: Matthew Jones Date: Wed, 21 Nov 2018 18:34:02 +1300 Subject: [PATCH] support firestore cloud function triggers (#2480) * support firestore cloud function triggers * document firestore trigger specifics --- google/resource_cloudfunctions_function.go | 25 ++++++++- .../resource_cloudfunctions_function_test.go | 56 +++++++++++++++++++ .../cloudfunctions/firestore_trigger.js | 13 +++++ .../r/cloudfunctions_function.html.markdown | 6 +- 4 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 google/test-fixtures/cloudfunctions/firestore_trigger.js diff --git a/google/resource_cloudfunctions_function.go b/google/resource_cloudfunctions_function.go index 12d91452..1c39f59d 100644 --- a/google/resource_cloudfunctions_function.go +++ b/google/resource_cloudfunctions_function.go @@ -535,6 +535,10 @@ func expandEventTrigger(configured []interface{}, project string) *cloudfunction shape = "projects/%s/buckets/%s" case strings.HasPrefix(eventType, "providers/cloud.pubsub/eventTypes/"): shape = "projects/%s/topics/%s" + case strings.HasPrefix(eventType, "providers/cloud.firestore/eventTypes/"): + // Firestore doesn't not yet support multiple databases, so "(default)" is assumed. + // https://cloud.google.com/functions/docs/calling/cloud-firestore#deploying_your_function + shape = "projects/%s/databases/(default)/documents/%s" } return &cloudfunctions.EventTrigger{ @@ -550,9 +554,28 @@ func flattenEventTrigger(eventTrigger *cloudfunctions.EventTrigger) []map[string return result } + resource := "" + switch { + case strings.HasPrefix(eventTrigger.EventType, "google.storage.object."): + resource = GetResourceNameFromSelfLink(eventTrigger.Resource) + case strings.HasPrefix(eventTrigger.EventType, "google.pubsub.topic."): + resource = GetResourceNameFromSelfLink(eventTrigger.Resource) + // Legacy style triggers + case strings.HasPrefix(eventTrigger.EventType, "providers/cloud.storage/eventTypes/"): + resource = GetResourceNameFromSelfLink(eventTrigger.Resource) + case strings.HasPrefix(eventTrigger.EventType, "providers/cloud.pubsub/eventTypes/"): + resource = GetResourceNameFromSelfLink(eventTrigger.Resource) + case strings.HasPrefix(eventTrigger.EventType, "providers/cloud.firestore/eventTypes/"): + // Simply taking the substring after the last "/" is not sufficient for firestore as resources may have slashes. + // For the eventTrigger.Resource "projects/my-project/databases/(default)/documents/messages/{messageId}" we extract + // the resource "messages/{messageId}" by taking the everything after the 5th "/" + parts := strings.SplitN(eventTrigger.Resource, "/", 6) + resource = parts[len(parts)-1] + } + result = append(result, map[string]interface{}{ "event_type": eventTrigger.EventType, - "resource": GetResourceNameFromSelfLink(eventTrigger.Resource), + "resource": resource, "failure_policy": flattenFailurePolicy(eventTrigger.FailurePolicy), }) diff --git a/google/resource_cloudfunctions_function_test.go b/google/resource_cloudfunctions_function_test.go index 1676e6a8..f82372d1 100644 --- a/google/resource_cloudfunctions_function_test.go +++ b/google/resource_cloudfunctions_function_test.go @@ -23,6 +23,7 @@ const testHTTPTriggerPath = "./test-fixtures/cloudfunctions/http_trigger.js" const testHTTPTriggerUpdatePath = "./test-fixtures/cloudfunctions/http_trigger_update.js" const testPubSubTriggerPath = "./test-fixtures/cloudfunctions/pubsub_trigger.js" const testBucketTriggerPath = "./test-fixtures/cloudfunctions/bucket_trigger.js" +const testFirestoreTriggerPath = "./test-fixtures/cloudfunctions/firestore_trigger.js" func TestAccCloudFunctionsFunction_basic(t *testing.T) { t.Parallel() @@ -205,6 +206,34 @@ func TestAccCloudFunctionsFunction_bucket(t *testing.T) { }) } +func TestAccCloudFunctionsFunction_firestore(t *testing.T) { + t.Parallel() + funcResourceName := "google_cloudfunctions_function.function" + functionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + bucketName := fmt.Sprintf("tf-test-bucket-%d", acctest.RandInt()) + zipFilePath, err := createZIPArchiveForIndexJs(testFirestoreTriggerPath) + 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: testAccCloudFunctionsFunction_firestore(functionName, bucketName, zipFilePath), + }, + { + ResourceName: funcResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckCloudFunctionsFunctionDestroy(s *terraform.State) error { config := testAccProvider.Meta().(*Config) @@ -508,3 +537,30 @@ resource "google_cloudfunctions_function" "function" { } }`, bucketName, zipFilePath, functionName) } + +func testAccCloudFunctionsFunction_firestore(functionName string, bucketName string, + zipFilePath 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" { + name = "%s" + available_memory_mb = 128 + source_archive_bucket = "${google_storage_bucket.bucket.name}" + source_archive_object = "${google_storage_bucket_object.archive.name}" + timeout = 61 + entry_point = "helloFirestore" + event_trigger { + event_type = "providers/cloud.firestore/eventTypes/document.write" + resource = "messages/{messageId}" + } +}`, bucketName, zipFilePath, functionName) +} diff --git a/google/test-fixtures/cloudfunctions/firestore_trigger.js b/google/test-fixtures/cloudfunctions/firestore_trigger.js new file mode 100644 index 00000000..f921b2fe --- /dev/null +++ b/google/test-fixtures/cloudfunctions/firestore_trigger.js @@ -0,0 +1,13 @@ +/** + * Background Cloud Function to be triggered by Firestore. + * + * @param {object} event The Cloud Functions event. + * @param {function} callback The callback function. + */ +exports.helloFirestore = function (event, callback) { + const messageId = event.params.messageId; + + console.log(`Received message ${messageId}`); + + callback(); +}; diff --git a/website/docs/r/cloudfunctions_function.html.markdown b/website/docs/r/cloudfunctions_function.html.markdown index f0947b01..5d4e3c7d 100644 --- a/website/docs/r/cloudfunctions_function.html.markdown +++ b/website/docs/r/cloudfunctions_function.html.markdown @@ -78,9 +78,9 @@ The `event_trigger` block supports: * `event_type` - (Required) The type of event to observe. For example: `"google.storage.object.finalize"`. See the documentation on [calling Cloud Functions](https://cloud.google.com/functions/docs/calling/) for a full reference. -Only Cloud Storage and Cloud Pub/Sub triggers are supported at this time. -Legacy Cloud Storage and Cloud Pub/Sub triggers are also supported, such as `"providers/cloud.storage/eventTypes/object.change"` -and `"providers/cloud.pubsub/eventTypes/topic.publish"`. +Cloud Storage, Cloud Pub/Sub and Cloud Firestore triggers are supported at this time. +Legacy triggers are supported, such as `"providers/cloud.storage/eventTypes/object.change"`, +`"providers/cloud.pubsub/eventTypes/topic.publish"` and `"providers/cloud.firestore/eventTypes/document.create"`. * `resource` - (Required) Required. The name of the resource from which to observe events, for example, `"myBucket"`