terraform-provider-google/google/resource_cloudfunctions_function_test.go
The Magician 1ba048535e Add a default to Cloud Functions runtime (#3605)
Signed-off-by: Modular Magician <magic-modules@google.com>
2019-05-09 15:50:26 -07:00

709 lines
21 KiB
Go

package google
import (
"bytes"
"fmt"
"os"
"testing"
"archive/zip"
"io/ioutil"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/cloudfunctions/v1"
)
const (
FUNCTION_TRIGGER_HTTP = iota
)
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 TestCloudFunctionsFunction_nameValidator(t *testing.T) {
validNames := []string{
"a",
"aA",
"a0",
"has-hyphen",
"has_underscore",
"hasUpperCase",
"allChars_-A0",
}
for _, tc := range validNames {
wrns, errs := validateResourceCloudFunctionsFunctionName(tc, "function.name")
if len(wrns) > 0 {
t.Errorf("Expected no validation warnings for test case %q, got: %+v", tc, wrns)
}
if len(errs) > 0 {
t.Errorf("Expected no validation errors for test name %q, got: %+v", tc, errs)
}
}
invalidNames := []string{
"0startsWithNumber",
"endsWith_",
"endsWith-",
"bad*Character",
"aFunctionsNameThatIsLongerThanFortyEightCharacters",
}
for _, tc := range invalidNames {
_, errs := validateResourceCloudFunctionsFunctionName(tc, "function.name")
if len(errs) == 0 {
t.Errorf("Expected errors for invalid test name %q, got none", tc)
}
}
}
func TestAccCloudFunctionsFunction_basic(t *testing.T) {
t.Parallel()
var function cloudfunctions.CloudFunction
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(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: testAccCloudFunctionsFunction_basic(functionName, bucketName, zipFilePath),
Check: resource.ComposeTestCheckFunc(
testAccCloudFunctionsFunctionExists(
funcResourceName, &function),
resource.TestCheckResourceAttr(funcResourceName,
"name", functionName),
resource.TestCheckResourceAttr(funcResourceName,
"description", "test function"),
resource.TestCheckResourceAttr(funcResourceName,
"available_memory_mb", "128"),
resource.TestCheckResourceAttr(funcResourceName,
"max_instances", "10"),
testAccCloudFunctionsFunctionSource(fmt.Sprintf("gs://%s/index.zip", bucketName), &function),
testAccCloudFunctionsFunctionTrigger(FUNCTION_TRIGGER_HTTP, &function),
resource.TestCheckResourceAttr(funcResourceName,
"timeout", "61"),
resource.TestCheckResourceAttr(funcResourceName,
"entry_point", "helloGET"),
resource.TestCheckResourceAttr(funcResourceName,
"trigger_http", "true"),
testAccCloudFunctionsFunctionHasLabel("my-label", "my-label-value", &function),
testAccCloudFunctionsFunctionHasEnvironmentVariable("TEST_ENV_VARIABLE",
"test-env-variable-value", &function),
),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccCloudFunctionsFunction_update(t *testing.T) {
t.Parallel()
var function cloudfunctions.CloudFunction
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(testHTTPTriggerPath)
zipFileUpdatePath, err := createZIPArchiveForIndexJs(testHTTPTriggerUpdatePath)
if err != nil {
t.Fatal(err.Error())
}
defer os.Remove(zipFilePath) // clean up
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCloudFunctionsFunction_basic(functionName, bucketName, zipFilePath),
Check: resource.ComposeTestCheckFunc(
testAccCloudFunctionsFunctionExists(
funcResourceName, &function),
resource.TestCheckResourceAttr(funcResourceName,
"available_memory_mb", "128"),
testAccCloudFunctionsFunctionHasLabel("my-label", "my-label-value", &function),
),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccCloudFunctionsFunction_updated(functionName, bucketName, zipFileUpdatePath),
Check: resource.ComposeTestCheckFunc(
testAccCloudFunctionsFunctionExists(
funcResourceName, &function),
resource.TestCheckResourceAttr(funcResourceName,
"available_memory_mb", "256"),
resource.TestCheckResourceAttr(funcResourceName,
"description", "test function updated"),
resource.TestCheckResourceAttr(funcResourceName,
"timeout", "91"),
resource.TestCheckResourceAttr(funcResourceName,
"max_instances", "15"),
testAccCloudFunctionsFunctionHasLabel("my-label", "my-updated-label-value", &function),
testAccCloudFunctionsFunctionHasLabel("a-new-label", "a-new-label-value", &function),
testAccCloudFunctionsFunctionHasEnvironmentVariable("TEST_ENV_VARIABLE",
"test-env-variable-value", &function),
testAccCloudFunctionsFunctionHasEnvironmentVariable("NEW_ENV_VARIABLE",
"new-env-variable-value", &function),
),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccCloudFunctionsFunction_pubsub(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())
topicName := fmt.Sprintf("tf-test-sub-%s", acctest.RandString(10))
zipFilePath, err := createZIPArchiveForIndexJs(testPubSubTriggerPath)
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_pubsub(functionName, bucketName,
topicName, zipFilePath),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccCloudFunctionsFunction_bucket(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(testBucketTriggerPath)
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_bucket(functionName, bucketName, zipFilePath),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccCloudFunctionsFunction_bucketNoRetry(functionName, bucketName, zipFilePath),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
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 TestAccCloudFunctionsFunction_sourceRepo(t *testing.T) {
t.Parallel()
funcResourceName := "google_cloudfunctions_function.function"
functionName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
proj := getTestProjectFromEnv()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFunctionsFunctionDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudFunctionsFunction_sourceRepo(functionName, proj),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccCloudFunctionsFunction_serviceAccountEmail(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(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: testAccCloudFunctionsFunction_serviceAccountEmail(functionName, bucketName, zipFilePath),
},
{
ResourceName: funcResourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccCheckCloudFunctionsFunctionDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(*Config)
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_cloudfunctions_function" {
continue
}
name := rs.Primary.Attributes["name"]
project := rs.Primary.Attributes["project"]
region := rs.Primary.Attributes["region"]
cloudFuncId := &cloudFunctionId{
Project: project,
Region: region,
Name: name,
}
_, err := config.clientCloudFunctions.Projects.Locations.Functions.Get(cloudFuncId.cloudFunctionId()).Do()
if err == nil {
return fmt.Errorf("Function still exists")
}
}
return nil
}
func testAccCloudFunctionsFunctionExists(n string, function *cloudfunctions.CloudFunction) resource.TestCheckFunc {
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)
name := rs.Primary.Attributes["name"]
project := rs.Primary.Attributes["project"]
region := rs.Primary.Attributes["region"]
cloudFuncId := &cloudFunctionId{
Project: project,
Region: region,
Name: name,
}
found, err := config.clientCloudFunctions.Projects.Locations.Functions.Get(cloudFuncId.cloudFunctionId()).Do()
if err != nil {
return fmt.Errorf("CloudFunctions Function not present")
}
*function = *found
return nil
}
}
func testAccCloudFunctionsFunctionSource(n string, function *cloudfunctions.CloudFunction) resource.TestCheckFunc {
return func(s *terraform.State) error {
if n != function.SourceArchiveUrl {
return fmt.Errorf("Expected source to be %v, got %v", n, function.EntryPoint)
}
return nil
}
}
func testAccCloudFunctionsFunctionTrigger(n int, function *cloudfunctions.CloudFunction) resource.TestCheckFunc {
return func(s *terraform.State) error {
switch n {
case FUNCTION_TRIGGER_HTTP:
if function.HttpsTrigger == nil {
return fmt.Errorf("Expected HttpsTrigger to be set")
}
default:
return fmt.Errorf("testAccCloudFunctionsFunctionTrigger expects only FUNCTION_TRIGGER_HTTP, ")
}
return nil
}
}
func testAccCloudFunctionsFunctionHasLabel(key, value string,
function *cloudfunctions.CloudFunction) resource.TestCheckFunc {
return func(s *terraform.State) error {
val, ok := function.Labels[key]
if !ok {
return fmt.Errorf("Label with key %s not found", key)
}
if val != value {
return fmt.Errorf("Label value did not match for key %s: expected %s but found %s", key, value, val)
}
return nil
}
}
func testAccCloudFunctionsFunctionHasEnvironmentVariable(key, value string,
function *cloudfunctions.CloudFunction) resource.TestCheckFunc {
return func(s *terraform.State) error {
if val, ok := function.EnvironmentVariables[key]; ok {
if val != value {
return fmt.Errorf("Environment Variable value did not match for key %s: expected %s but found %s",
key, value, val)
}
} else {
return fmt.Errorf("Environment Variable with key %s not found", key)
}
return nil
}
}
func createZIPArchiveForIndexJs(sourcePath string) (string, error) {
source, err := ioutil.ReadFile(sourcePath)
if err != nil {
return "", err
}
// Create a buffer to write our archive to.
buf := new(bytes.Buffer)
// Create a new zip archive.
w := zip.NewWriter(buf)
f, err := w.Create("index.js")
if err != nil {
return "", err
}
_, err = f.Write(source)
if err != nil {
return "", err
}
// Make sure to check the error on Close.
err = w.Close()
if err != nil {
return "", err
}
// Create temp file to write zip to
tmpfile, err := ioutil.TempFile("", "zip")
if err != nil {
return "", err
}
if _, err := tmpfile.Write(buf.Bytes()); err != nil {
return "", err
}
if err := tmpfile.Close(); err != nil {
return "", err
}
return tmpfile.Name(), nil
}
func testAccCloudFunctionsFunction_basic(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"
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"
labels = {
my-label = "my-label-value"
}
environment_variables = {
TEST_ENV_VARIABLE = "test-env-variable-value"
}
max_instances = 10
}
`, bucketName, zipFilePath, functionName)
}
func testAccCloudFunctionsFunction_updated(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_update.zip"
bucket = "${google_storage_bucket.bucket.name}"
source = "%s"
}
resource "google_cloudfunctions_function" "function" {
name = "%s"
description = "test function updated"
available_memory_mb = 256
source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
trigger_http = true
runtime = "nodejs8"
timeout = 91
entry_point = "helloGET"
labels = {
my-label = "my-updated-label-value"
a-new-label = "a-new-label-value"
}
environment_variables = {
TEST_ENV_VARIABLE = "test-env-variable-value"
NEW_ENV_VARIABLE = "new-env-variable-value"
}
max_instances = 15
}`, bucketName, zipFilePath, functionName)
}
func testAccCloudFunctionsFunction_pubsub(functionName string, bucketName string,
topic 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_pubsub_topic" "sub" {
name = "%s"
}
resource "google_cloudfunctions_function" "function" {
name = "%s"
runtime = "nodejs8"
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 = "helloPubSub"
event_trigger {
event_type = "providers/cloud.pubsub/eventTypes/topic.publish"
resource = "${google_pubsub_topic.sub.name}"
failure_policy {
retry = false
}
}
}`, bucketName, zipFilePath, topic, functionName)
}
func testAccCloudFunctionsFunction_bucket(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"
runtime = "nodejs6"
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 = "helloGCS"
event_trigger {
event_type = "google.storage.object.finalize"
resource = "${google_storage_bucket.bucket.name}"
failure_policy {
retry = true
}
}
}`, bucketName, zipFilePath, functionName)
}
func testAccCloudFunctionsFunction_bucketNoRetry(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 = "helloGCS"
event_trigger {
event_type = "google.storage.object.finalize"
resource = "${google_storage_bucket.bucket.name}"
}
}`, 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)
}
func testAccCloudFunctionsFunction_sourceRepo(functionName, project string) string {
return fmt.Sprintf(`
resource "google_cloudfunctions_function" "function" {
name = "%s"
source_repository {
// There isn't yet an API that'll allow us to create a source repository and
// put code in it, so we created this repository outside the test to be used
// here. If this test is run outside of CI, you may need to create your own
// source repo.
url = "https://source.developers.google.com/projects/%s/repos/cloudfunctions-test-do-not-delete/moveable-aliases/master/paths/"
}
trigger_http = true
entry_point = "helloGET"
}
`, functionName, project)
}
func testAccCloudFunctionsFunction_serviceAccountEmail(functionName, bucketName, 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"
}
data "google_compute_default_service_account" "default" { }
resource "google_cloudfunctions_function" "function" {
name = "%s"
source_archive_bucket = "${google_storage_bucket.bucket.name}"
source_archive_object = "${google_storage_bucket_object.archive.name}"
service_account_email = "${data.google_compute_default_service_account.default.email}"
trigger_http = true
entry_point = "helloGET"
}`, bucketName, zipFilePath, functionName)
}