From ca85988291c2128beda0c79866daef67bdf93037 Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Fri, 28 Jan 2022 14:59:26 -0800 Subject: [PATCH 01/11] Add SecretManager integration support to GCF. --- .../resource_cloudfunctions_function.go | 209 ++++++++++++++ ...source_cloudfunctions_function_test.go.erb | 272 ++++++++++++++++++ .../r/cloudfunctions_function.html.markdown | 30 ++ 3 files changed, 511 insertions(+) diff --git a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go index 3925c3feaad1..f45d0f8b516a 100644 --- a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go +++ b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go @@ -315,6 +315,80 @@ func resourceCloudFunctionsFunction() *schema.Resource { ForceNew: true, Description: `Region of function. If it is not provided, the provider region is used.`, }, + + "secret_environment_variables": { + Type: schema.TypeList, + Optional: true, + Description: `Secret environment variables configuration`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + Description: `Name of the environment variable.`, + }, + "project_id": { + Type: schema.TypeString, + Computed: true, + Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, + }, + "secret": { + Type: schema.TypeString, + Required: true, + Description: `ID of the secret in secret manager (not the full resource name).`, + }, + "version": { + Type: schema.TypeString, + Required: true, + Description: `Version of the secret (version number or the string "latest"). It is recommended to use a numeric version for secret environment variables as any updates to the secret value is not reflected until new clones start.`, + }, + }, + }, + }, + + "secret_volumes": { + Type: schema.TypeList, + Optional: true, + Description: `Secret volumes configuration.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mount_path": { + Type: schema.TypeString, + Required: true, + Description: `The path within the container to mount the secret volume. For example, setting the mount_path as "/etc/secrets" would mount the secret value files under the "/etc/secrets" directory. This directory will also be completely shadowed and unavailable to mount any other secrets. Recommended mount paths: "/etc/secrets" Restricted mount paths: "/cloudsql", "/dev/log", "/pod", "/proc", "/var/log".`, + }, + "project_id": { + Type: schema.TypeString, + Computed: true, + Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, + }, + "secret": { + Type: schema.TypeString, + Required: true, + Description: `ID of the secret in secret manager (not the full resource name).`, + }, + "versions": { + Type: schema.TypeList, + Optional: true, + Description: `List of secret versions to mount for this secret. If empty, the "latest" version of the secret will be made available in a file named after the secret under the mount point.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "path": { + Type: schema.TypeString, + Required: true, + Description: `Relative path of the file under the mount path where the secret value for this version will be fetched and made available. For example, setting the mount_path as "/etc/secrets" and path as "/secret_foo" would mount the secret value file at "/etc/secrets/secret_foo".`, + }, + "version": { + Type: schema.TypeString, + Required: true, + Description: `Version of the secret (version number or the string "latest"). It is preferable to use "latest" version with secret volumes as secret value changes are reflected immediately.`, + }, + }, + }, + }, + }, + }, + }, }, UseJSONNumber: true, } @@ -362,6 +436,16 @@ func resourceCloudFunctionsCreate(d *schema.ResourceData, meta interface{}) erro function.SourceArchiveUrl = fmt.Sprintf("gs://%v/%v", sourceArchiveBucket, sourceArchiveObj) } + secretEnv := d.Get("secret_environment_variables").([]interface{}) + if len(secretEnv) > 0 { + function.SecretEnvironmentVariables = expandSecretEnvironmentVariables(secretEnv) + } + + secretVolume := d.Get("secret_volumes").([]interface{}) + if len(secretVolume) > 0 { + function.SecretVolumes = expandSecretVolumes(secretVolume) + } + if v, ok := d.GetOk("available_memory_mb"); ok { availableMemoryMb := v.(int) function.AvailableMemoryMb = int64(availableMemoryMb) @@ -523,6 +607,14 @@ func resourceCloudFunctionsRead(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("Error setting source_repository: %s", err) } + if err := d.Set("secret_environment_variables", flattenSecretEnvironmentVariables(function.SecretEnvironmentVariables)); err != nil { + return fmt.Errorf("Error setting secret_environment_variables: %s", err) + } + + if err := d.Set("secret_volumes", flattenSecretVolumes(function.SecretVolumes)); err != nil { + return fmt.Errorf("Error setting secret_volumes: %s", err) + } + if function.HttpsTrigger != nil { if err := d.Set("trigger_http", true); err != nil { return fmt.Errorf("Error setting trigger_http: %s", err) @@ -600,6 +692,16 @@ func resourceCloudFunctionsUpdate(d *schema.ResourceData, meta interface{}) erro updateMaskArr = append(updateMaskArr, "sourceRepository") } + if d.HasChange("secret_environment_variables") { + function.SecretEnvironmentVariables = expandSecretEnvironmentVariables(d.Get("secret_environment_variables").([]interface{})) + updateMaskArr = append(updateMaskArr, "secretEnvironmentVariables") + } + + if d.HasChange("secret_volumes") { + function.SecretVolumes = expandSecretVolumes(d.Get("secret_volumes").([]interface{})) + updateMaskArr = append(updateMaskArr, "secretVolumes") + } + if d.HasChange("description") { function.Description = d.Get("description").(string) updateMaskArr = append(updateMaskArr, "description") @@ -818,3 +920,110 @@ func flattenSourceRepository(sourceRepo *cloudfunctions.SourceRepository) []map[ return result } + +func expandSecretEnvironmentVariables(configured []interface{}) []*cloudfunctions.SecretEnvVar { + if len(configured) == 0 { + return nil + } + result := make([]*cloudfunctions.SecretEnvVar, 0, len(configured)) + for _, e := range configured { + data := e.(map[string]interface{}) + result = append(result, &cloudfunctions.SecretEnvVar{ + Key: data["key"].(string), + ProjectId: data["project_id"].(string), + Secret: data["secret"].(string), + Version: data["version"].(string), + }) + } + return result +} + +func flattenSecretEnvironmentVariables(envVars []*cloudfunctions.SecretEnvVar) []map[string]interface{} { + if envVars == nil { + return nil + } + var result []map[string]interface{} + + for _, envVar := range envVars { + if envVar != nil { + data := map[string]interface{}{ + "key": envVar.Key, + "project_id": envVar.ProjectId, + "secret": envVar.Secret, + "version": envVar.Version, + } + result = append(result, data) + } + } + return result +} + +func expandSecretVolumes(configured []interface{}) []*cloudfunctions.SecretVolume { + if len(configured) == 0 { + return nil + } + result := make([]*cloudfunctions.SecretVolume, 0, len(configured)) + for _, e := range configured { + data := e.(map[string]interface{}) + result = append(result, &cloudfunctions.SecretVolume{ + MountPath: data["mount_path"].(string), + ProjectId: data["project_id"].(string), + Secret: data["secret"].(string), + Versions: expandSecretVersion(data["versions"].([]interface{})), //TODO + }) + } + return result +} + +func flattenSecretVolumes(secretVolumes []*cloudfunctions.SecretVolume) []map[string]interface{} { + if secretVolumes == nil { + return nil + } + var result []map[string]interface{} + + for _, secretVolume := range secretVolumes { + if secretVolume != nil { + data := map[string]interface{}{ + "mount_path": secretVolume.MountPath, + "project_id": secretVolume.ProjectId, + "secret": secretVolume.Secret, + "versions": flattenSecretVersion(secretVolume.Versions), + } + result = append(result, data) + } + } + return result +} + +func expandSecretVersion(configured []interface{}) []*cloudfunctions.SecretVersion { + if len(configured) == 0 { + return nil + } + result := make([]*cloudfunctions.SecretVersion, 0, len(configured)) + for _, e := range configured { + data := e.(map[string]interface{}) + result = append(result, &cloudfunctions.SecretVersion{ + Path: data["path"].(string), + Version: data["version"].(string), + }) + } + return result +} + +func flattenSecretVersion(secretVersions []*cloudfunctions.SecretVersion) []map[string]interface{} { + if secretVersions == nil { + return nil + } + var result []map[string]interface{} + + for _, secretVersion := range secretVersions { + if secretVersion != nil { + data := map[string]interface{}{ + "path": secretVersion.Path, + "version": secretVersion.Version, + } + result = append(result, data) + } + } + return result +} diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index a02a153d923b..0b1e7352902e 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -421,6 +421,86 @@ func TestAccCloudFunctionsFunction_vpcConnector(t *testing.T) { }) } +func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { + t.Parallel() + + projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") + secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randString()) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randString()) + bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) + functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + funcResourceName := "google_cloudfunctions_function.function" + + defer os.Remove(zipFilePath) // clean up + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + }, + { + ResourceName: funcResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"build_environment_variables"}, + }, + { + Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2"), + }, + { + ResourceName: funcResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"build_environment_variables"}, + }, + }, + }) +} + +func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { + t.Parallel() + + projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") + secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randString()) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randString()) + bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) + functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + funcResourceName := "google_cloudfunctions_function.function" + + defer os.Remove(zipFilePath) // clean up + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + }, + { + ResourceName: funcResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"build_environment_variables"}, + }, + { + Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName, functionName, "2"), + }, + { + ResourceName: funcResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"build_environment_variables"}, + }, + }, + }) +} + func testAccCheckCloudFunctionsFunctionDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := googleProviderConfig(t) @@ -918,3 +998,195 @@ resource "google_cloudfunctions_function" "function" { } `, projectNumber, networkName, vpcConnectorName, vpcConnectorName, vpcIp, bucketName, zipFilePath, functionName, vpcConnectorName) } + +func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber string) string { + return fmt.Sprintf(` +data "google_project" "project" {} + +resource "google_project_iam_member" "gcfadmin" { + project = data.google_project.project.project_id + role = "roles/editor" + member = "serviceAccount:service-%s@gcf-admin-robot.iam.gserviceaccount.com" +} + +resource "google_service_account" "cloud_function_runner" { + account_id = "cloud-function-service" + display_name = "Testing Cloud Function Secrets integration" +} + +resource "google_secret_manager_secret" "test_secret" { + secret_id = "%s" + + replication { + user_managed { + replicas { + location = "us-central1" + } + replicas { + location = "us-east1" + } + } + } + depends_on = [ + google_project_service.secret_manager + ] +} + +resource "google_secret_manager_secret_version" "%s" { + secret = google_secret_manager_secret.test_secret.id + secret_data = "This is my secret data." +} + +resource "google_secret_manager_secret_iam_member" "cloud_function_iam_member" { + secret_id = google_secret_manager_secret.test_secret.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.cloud_function_runner.email}" +} + +resource "google_storage_bucket" "cloud_functions" { + name = "%s" + location = "US" + uniform_bucket_level_access = true +} + +data "archive_file" "cloud_function_zip" { + type = "zip" + output_path = "/tmp/cloud_function.zip" + source { + content = <<-EOF + exports.echoSecret = (req, res) => { + let message = req.query.message || req.body.message || "Secret: "+process.env.MY_SECRET; + res.status(200).send(message); + }; + EOF + filename = "index.js" + } +} + +resource "google_storage_bucket_object" "cloud_function_zip_object" { + name = "cloud-function.zip" + bucket = google_storage_bucket.cloud_functions.id + source = data.archive_file.cloud_function_zip.output_path +} + +resource "google_cloudfunctions_function" "secrets_test" { + name = "%s" + runtime = "nodejs14" + service_account_email = google_service_account.cloud_function_runner.email + entry_point = "echoSecret" + source_archive_bucket = google_storage_bucket.cloud_functions.id + source_archive_object = google_storage_bucket_object.cloud_function_zip_object.name + trigger_http = true + secret_environment_variables { + key = "MY_SECRET" + secret = google_secret_manager_secret.test_secret.secret_id + version = "%s" + } + + depends_on = [google_project_iam_member.gcfadmin] +} +`, projectNumber, secretName, versionName, bucketName, functionName, versionNumber) +} + +func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber string) string { + return fmt.Sprintf(` +data "google_project" "project" {} + +resource "google_project_iam_member" "gcfadmin" { + project = data.google_project.project.project_id + role = "roles/editor" + member = "serviceAccount:service-%s@gcf-admin-robot.iam.gserviceaccount.com" +} + +resource "google_service_account" "cloud_function_runner" { + account_id = "cloud-function-service" + display_name = "Testing Cloud Function Secrets integration" +} + +resource "google_secret_manager_secret" "test_secret" { + secret_id = "%s" + + replication { + user_managed { + replicas { + location = "us-central1" + } + replicas { + location = "us-east1" + } + } + } + depends_on = [ + google_project_service.secret_manager + ] +} + +resource "google_secret_manager_secret_version" "%s" { + secret = google_secret_manager_secret.test_secret.secret_id + secret_data = "This is my secret data." +} + +resource "google_secret_manager_secret_iam_member" "cloud_function_iam_member" { + secret_id = google_secret_manager_secret.test_secret.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.cloud_function_runner.email}" +} + +resource "google_storage_bucket" "cloud_functions" { + name = "%s" + location = "US" + uniform_bucket_level_access = true +} + +data "archive_file" "cloud_function_zip" { + type = "zip" + output_path = "/tmp/cloud_function_2.zip" + source { + content = <<-EOF + const fs = require('fs') + exports.echoSecret = (req, res) => { + const path = '/etc/secrets/test-secret' + fs.access(path, fs.F_OK, (err) => { + if (err) { + console.error(err) + res.status(200).send(err) + return + } + fs.readFile(path, 'utf8', function(err,data) { + res.status(200).send("Secret: "+data) + return + }); + }) + }; + EOF + filename = "index.js" + } +} + +resource "google_storage_bucket_object" "cloud_function_zip_object" { + name = "cloud-function.zip" + bucket = google_storage_bucket.cloud_functions.id + source = data.archive_file.cloud_function_zip.output_path +} + +resource "google_cloudfunctions_function" "secrets_test" { + name = "%s" + runtime = "nodejs14" + service_account_email = google_service_account.cloud_function_runner.email + entry_point = "echoSecret" + source_archive_bucket = google_storage_bucket.cloud_functions.id + source_archive_object = google_storage_bucket_object.cloud_function_zip_object.name + trigger_http = true + secret_volumes { + secret = google_secret_manager_secret.test_secret.secret_id + mount_path = "/etc/secrets" + versions { + version = "%s" + path = "/test-secret" + } + } + + depends_on = [google_project_iam_member.gcfadmin] +} +`, projectNumber, secretName, versionName, bucketName, functionName, versionNumber) +} \ No newline at end of file diff --git a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown index aa1b86cde4ea..6a2689aa1005 100644 --- a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown @@ -152,6 +152,10 @@ Eg. `"nodejs10"`, `"nodejs12"`, `"nodejs14"`, `"python37"`, `"python38"`, `"pyth * `min_instances` - (Optional) The limit on the minimum number of function instances that may coexist at a given time. +* `secret_environment_variables` - (Optional) Secret environment variables configuration. Structure is [documented below](#nested_secret_environment_variables). + +* `secret_volumes` - (Optional) Secret volumes configuration. Structure is [documented below](#nested_secret_volumes). + The `event_trigger` block supports: * `event_type` - (Required) The type of event to observe. For example: `"google.storage.object.finalize"`. @@ -175,6 +179,32 @@ which to observe events. For example, `"myBucket"` or `"projects/my-project/topi * To refer to a moveable alias (branch): `https://source.developers.google.com/projects/*/repos/*/moveable-aliases/*/paths/*`. To refer to HEAD, use the `master` moveable alias. * To refer to a specific fixed alias (tag): `https://source.developers.google.com/projects/*/repos/*/fixed-aliases/*/paths/*` +The `secret_environment_variables` block supports: + +* `key` - (Required) Name of the environment variable. + +* `project_id` - (Required) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. + +* `secret` - (Required) ID of the secret in secret manager (not the full resource name). + +* `version` - (Required) Version of the secret (version number or the string "latest"). It is recommended to use a numeric version for secret environment variables as any updates to the secret value is not reflected until new clones start. + +The `secret_volumes` block supports: + +* `mount_path` - (Required) The path within the container to mount the secret volume. For example, setting the mount_path as "/etc/secrets" would mount the secret value files under the "/etc/secrets" directory. This directory will also be completely shadowed and unavailable to mount any other secrets. Recommended mount paths: "/etc/secrets" Restricted mount paths: "/cloudsql", "/dev/log", "/pod", "/proc", "/var/log". + +* `project_id` - (Required) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. + +* `secret` - (Required) ID of the secret in secret manager (not the full resource name). + +* `versions` - (Required) List of secret versions to mount for this secret. If empty, the "latest" version of the secret will be made available in a file named after the secret under the mount point. Structure is [documented below](#nested_nested_versions). + +The `versions` block supports: + +* `path` - (Required) Relative path of the file under the mount path where the secret value for this version will be fetched and made available. For example, setting the mount_path as "/etc/secrets" and path as "/secret_foo" would mount the secret value file at "/etc/secrets/secret_foo". + +* `version` - (Required) Version of the secret (version number or the string "latest"). It is preferable to use "latest" version with secret volumes as secret value changes are reflected immediately. + ## Attributes Reference In addition to the arguments listed above, the following computed attributes are From 233af87dd221bf60cf389f46fbabd9fa6f3e0366 Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Fri, 28 Jan 2022 15:32:59 -0800 Subject: [PATCH 02/11] bug fix --- ...source_cloudfunctions_function_test.go.erb | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index 0b1e7352902e..e09a0b1a6413 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -426,21 +426,19 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) - versionName1 := fmt.Sprintf("tf-test-version1-%s", randString()) - versionName2 := fmt.Sprintf("tf-test-version2-%s", randString()) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randString(t, 10)) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) funcResourceName := "google_cloudfunctions_function.function" - defer os.Remove(zipFilePath) // clean up - vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), }, { ResourceName: funcResourceName, @@ -449,7 +447,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2"), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2"), }, { ResourceName: funcResourceName, @@ -466,21 +464,19 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) - versionName1 := fmt.Sprintf("tf-test-version1-%s", randString()) - versionName2 := fmt.Sprintf("tf-test-version2-%s", randString()) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randString(t, 10)) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) funcResourceName := "google_cloudfunctions_function.function" - defer os.Remove(zipFilePath) // clean up - vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName1, bucketName, functionName, "1"), }, { ResourceName: funcResourceName, @@ -489,7 +485,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: TestAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName, functionName, "2"), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName2, bucketName, functionName, "2"), }, { ResourceName: funcResourceName, From c570c46d094a34e30a6370d88e266a298d846ffb Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Fri, 28 Jan 2022 17:38:01 -0800 Subject: [PATCH 03/11] Correcting field requirements. --- .../terraform/resources/resource_cloudfunctions_function.go | 4 ++-- .../website/docs/r/cloudfunctions_function.html.markdown | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go index f45d0f8b516a..0f824aa54352 100644 --- a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go +++ b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go @@ -329,7 +329,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { }, "project_id": { Type: schema.TypeString, - Computed: true, + Optional: true, Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, }, "secret": { @@ -359,7 +359,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { }, "project_id": { Type: schema.TypeString, - Computed: true, + Optional: true, Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, }, "secret": { diff --git a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown index 6a2689aa1005..d05022d50ccb 100644 --- a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown @@ -183,7 +183,7 @@ which to observe events. For example, `"myBucket"` or `"projects/my-project/topi * `key` - (Required) Name of the environment variable. -* `project_id` - (Required) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. +* `project_id` - (Optional) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. * `secret` - (Required) ID of the secret in secret manager (not the full resource name). @@ -193,11 +193,11 @@ which to observe events. For example, `"myBucket"` or `"projects/my-project/topi * `mount_path` - (Required) The path within the container to mount the secret volume. For example, setting the mount_path as "/etc/secrets" would mount the secret value files under the "/etc/secrets" directory. This directory will also be completely shadowed and unavailable to mount any other secrets. Recommended mount paths: "/etc/secrets" Restricted mount paths: "/cloudsql", "/dev/log", "/pod", "/proc", "/var/log". -* `project_id` - (Required) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. +* `project_id` - (Optional) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. * `secret` - (Required) ID of the secret in secret manager (not the full resource name). -* `versions` - (Required) List of secret versions to mount for this secret. If empty, the "latest" version of the secret will be made available in a file named after the secret under the mount point. Structure is [documented below](#nested_nested_versions). +* `versions` - (Optional) List of secret versions to mount for this secret. If empty, the "latest" version of the secret will be made available in a file named after the secret under the mount point. Structure is [documented below](#nested_nested_versions). The `versions` block supports: From 0aa4cbe87b261c7b2f72212c8f0debe25fbc2184 Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Mon, 31 Jan 2022 10:37:41 -0800 Subject: [PATCH 04/11] Using zips created as part of the test. --- ...source_cloudfunctions_function_test.go.erb | 71 +++++-------------- .../secret_environment_variables.js | 7 ++ .../cloudfunctions/secret_volumes_mount.js | 18 +++++ 3 files changed, 44 insertions(+), 52 deletions(-) create mode 100644 mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_environment_variables.js create mode 100644 mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_volumes_mount.js diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index e09a0b1a6413..41742cb14f7a 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -26,6 +26,8 @@ const testHTTPTriggerUpdatePath = "./test-fixtures/cloudfunctions/http_trigger_u const testPubSubTriggerPath = "./test-fixtures/cloudfunctions/pubsub_trigger.js" const testBucketTriggerPath = "./test-fixtures/cloudfunctions/bucket_trigger.js" const testFirestoreTriggerPath = "./test-fixtures/cloudfunctions/firestore_trigger.js" +const testSecretEnvVarFunctionPath = "./test-fixtures/cloudfunctions/secret_environment_variables.js" +const testSecretVolumesMountFunctionPath = "./test-fixtures/cloudfunctions/secret_volumes_mount.js" const testFunctionsSourceArchivePrefix = "cloudfunczip" func init() { @@ -430,7 +432,9 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + zipFilePath := createZIPArchiveForCloudFunctionSource(t, testSecretEnvVarFunctionPath) funcResourceName := "google_cloudfunctions_function.function" + defer os.Remove(zipFilePath) // clean up vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -438,7 +442,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath), }, { ResourceName: funcResourceName, @@ -447,7 +451,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2"), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2", zipFilePath), }, { ResourceName: funcResourceName, @@ -468,7 +472,9 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) - funcResourceName := "google_cloudfunctions_function.function" + zipFilePath := createZIPArchiveForCloudFunctionSource(t, testSecretVolumesMountFunctionPath) + funcResourceName := "google_cloudfunctions_function.function" + defer os.Remove(zipFilePath) // clean up vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -476,7 +482,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName1, bucketName, functionName, "1"), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath), }, { ResourceName: funcResourceName, @@ -485,7 +491,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName2, bucketName, functionName, "2"), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName2, bucketName, functionName, "2", zipFilePath), }, { ResourceName: funcResourceName, @@ -995,7 +1001,7 @@ resource "google_cloudfunctions_function" "function" { `, projectNumber, networkName, vpcConnectorName, vpcConnectorName, vpcIp, bucketName, zipFilePath, functionName, vpcConnectorName) } -func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber string) string { +func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath string) string { return fmt.Sprintf(` data "google_project" "project" {} @@ -1045,24 +1051,10 @@ resource "google_storage_bucket" "cloud_functions" { uniform_bucket_level_access = true } -data "archive_file" "cloud_function_zip" { - type = "zip" - output_path = "/tmp/cloud_function.zip" - source { - content = <<-EOF - exports.echoSecret = (req, res) => { - let message = req.query.message || req.body.message || "Secret: "+process.env.MY_SECRET; - res.status(200).send(message); - }; - EOF - filename = "index.js" - } -} - resource "google_storage_bucket_object" "cloud_function_zip_object" { name = "cloud-function.zip" - bucket = google_storage_bucket.cloud_functions.id - source = data.archive_file.cloud_function_zip.output_path + bucket = google_storage_bucket.cloud_functions.name + source = "%s" } resource "google_cloudfunctions_function" "secrets_test" { @@ -1081,10 +1073,10 @@ resource "google_cloudfunctions_function" "secrets_test" { depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, functionName, versionNumber) +`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber) } -func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber string) string { +func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath string) string { return fmt.Sprintf(` data "google_project" "project" {} @@ -1134,35 +1126,10 @@ resource "google_storage_bucket" "cloud_functions" { uniform_bucket_level_access = true } -data "archive_file" "cloud_function_zip" { - type = "zip" - output_path = "/tmp/cloud_function_2.zip" - source { - content = <<-EOF - const fs = require('fs') - exports.echoSecret = (req, res) => { - const path = '/etc/secrets/test-secret' - fs.access(path, fs.F_OK, (err) => { - if (err) { - console.error(err) - res.status(200).send(err) - return - } - fs.readFile(path, 'utf8', function(err,data) { - res.status(200).send("Secret: "+data) - return - }); - }) - }; - EOF - filename = "index.js" - } -} - resource "google_storage_bucket_object" "cloud_function_zip_object" { name = "cloud-function.zip" - bucket = google_storage_bucket.cloud_functions.id - source = data.archive_file.cloud_function_zip.output_path + bucket = google_storage_bucket.cloud_functions.name + source = "%s" } resource "google_cloudfunctions_function" "secrets_test" { @@ -1184,5 +1151,5 @@ resource "google_cloudfunctions_function" "secrets_test" { depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, functionName, versionNumber) +`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber) } \ No newline at end of file diff --git a/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_environment_variables.js b/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_environment_variables.js new file mode 100644 index 000000000000..60c4fe52fe6e --- /dev/null +++ b/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_environment_variables.js @@ -0,0 +1,7 @@ +/** + * HTTP Cloud Function for testing environment variable Secrets. + */ +exports.echoSecret = (req, res) => { + let message = req.query.message || req.body.message || "Secret: " + process.env.MY_SECRET; + res.status(200).send(message); +}; \ No newline at end of file diff --git a/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_volumes_mount.js b/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_volumes_mount.js new file mode 100644 index 000000000000..3b100a8237b4 --- /dev/null +++ b/mmv1/third_party/terraform/utils/test-fixtures/cloudfunctions/secret_volumes_mount.js @@ -0,0 +1,18 @@ +/** + * HTTP Cloud Function for testing volume mount Secrets. + */ +const fs = require('fs') +exports.echoSecret = (req, res) => { + const path = '/etc/secrets/test-secret' + fs.access(path, fs.F_OK, (err) => { + if (err) { + console.error(err) + res.status(200).send(err) + return + } + fs.readFile(path, 'utf8', function (err, data) { + res.status(200).send("Secret: " + data) + + }); + }) +}; \ No newline at end of file From 7d4aa439691315325e117cf87e54b21342cae7ee Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Mon, 31 Jan 2022 14:35:39 -0800 Subject: [PATCH 05/11] bug fix. --- ...source_cloudfunctions_function_test.go.erb | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index 41742cb14f7a..c0515ab43f49 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -427,11 +427,12 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { t.Parallel() projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") - secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) - versionName1 := fmt.Sprintf("tf-test-version1-%s", randString(t, 10)) - versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) + randomSecretSuffix := randString(t, 10) + secretName := fmt.Sprintf("tf-test-secret-%s", randomSecretSuffix) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randomSecretSuffix) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randomSecretSuffix) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) - functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + functionName := fmt.Sprintf("tf-test-%s", randomSecretSuffix) zipFilePath := createZIPArchiveForCloudFunctionSource(t, testSecretEnvVarFunctionPath) funcResourceName := "google_cloudfunctions_function.function" defer os.Remove(zipFilePath) // clean up @@ -467,11 +468,12 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { t.Parallel() projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") - secretName := fmt.Sprintf("tf-test-secret-%s", randString(t, 10)) - versionName1 := fmt.Sprintf("tf-test-version1-%s", randString(t, 10)) - versionName2 := fmt.Sprintf("tf-test-version2-%s", randString(t, 10)) + randomSecretSuffix := randString(t, 10) + secretName := fmt.Sprintf("tf-test-secret-%s", randomSecretSuffix) + versionName1 := fmt.Sprintf("tf-test-version1-%s", randomSecretSuffix) + versionName2 := fmt.Sprintf("tf-test-version2-%s", randomSecretSuffix) bucketName := fmt.Sprintf("tf-test-bucket-%d", randInt(t)) - functionName := fmt.Sprintf("tf-test-%s", randString(t, 10)) + functionName := fmt.Sprintf("tf-test-%s", randomSecretSuffix) zipFilePath := createZIPArchiveForCloudFunctionSource(t, testSecretVolumesMountFunctionPath) funcResourceName := "google_cloudfunctions_function.function" defer os.Remove(zipFilePath) // clean up @@ -1029,9 +1031,6 @@ resource "google_secret_manager_secret" "test_secret" { } } } - depends_on = [ - google_project_service.secret_manager - ] } resource "google_secret_manager_secret_version" "%s" { @@ -1057,7 +1056,7 @@ resource "google_storage_bucket_object" "cloud_function_zip_object" { source = "%s" } -resource "google_cloudfunctions_function" "secrets_test" { +resource "google_cloudfunctions_function" "function" { name = "%s" runtime = "nodejs14" service_account_email = google_service_account.cloud_function_runner.email @@ -1069,11 +1068,12 @@ resource "google_cloudfunctions_function" "secrets_test" { key = "MY_SECRET" secret = google_secret_manager_secret.test_secret.secret_id version = "%s" + project_id = "%s" } depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber) +`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber, projectNumber) } func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath string) string { @@ -1104,13 +1104,10 @@ resource "google_secret_manager_secret" "test_secret" { } } } - depends_on = [ - google_project_service.secret_manager - ] } resource "google_secret_manager_secret_version" "%s" { - secret = google_secret_manager_secret.test_secret.secret_id + secret = google_secret_manager_secret.test_secret.id secret_data = "This is my secret data." } @@ -1132,7 +1129,7 @@ resource "google_storage_bucket_object" "cloud_function_zip_object" { source = "%s" } -resource "google_cloudfunctions_function" "secrets_test" { +resource "google_cloudfunctions_function" "function" { name = "%s" runtime = "nodejs14" service_account_email = google_service_account.cloud_function_runner.email @@ -1143,6 +1140,7 @@ resource "google_cloudfunctions_function" "secrets_test" { secret_volumes { secret = google_secret_manager_secret.test_secret.secret_id mount_path = "/etc/secrets" + project_id = "%s" versions { version = "%s" path = "/test-secret" @@ -1151,5 +1149,5 @@ resource "google_cloudfunctions_function" "secrets_test" { depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber) +`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, projectNumber, versionNumber) } \ No newline at end of file From 244b61892a3b757bd76241a26670f86c4f97df53 Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Tue, 1 Feb 2022 17:08:41 -0800 Subject: [PATCH 06/11] addressing review comments. --- ...source_cloudfunctions_function_test.go.erb | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index c0515ab43f49..f09072a2eff4 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -428,6 +428,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") randomSecretSuffix := randString(t, 10) + accountId := fmt.Sprintf("tf-test-account-%s", randomSecretSuffix) secretName := fmt.Sprintf("tf-test-secret-%s", randomSecretSuffix) versionName1 := fmt.Sprintf("tf-test-version1-%s", randomSecretSuffix) versionName2 := fmt.Sprintf("tf-test-version2-%s", randomSecretSuffix) @@ -443,7 +444,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -452,7 +453,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2", zipFilePath), + Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -469,6 +470,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") randomSecretSuffix := randString(t, 10) + accountId := fmt.Sprintf("tf-test-account-%s", randomSecretSuffix) secretName := fmt.Sprintf("tf-test-secret-%s", randomSecretSuffix) versionName1 := fmt.Sprintf("tf-test-version1-%s", randomSecretSuffix) versionName2 := fmt.Sprintf("tf-test-version2-%s", randomSecretSuffix) @@ -484,7 +486,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -493,7 +495,7 @@ func TestAccCloudFunctionsFunction_secretMount(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName2, bucketName, functionName, "2", zipFilePath), + Config: testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName2, bucketName, functionName, "2", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -1003,18 +1005,12 @@ resource "google_cloudfunctions_function" "function" { `, projectNumber, networkName, vpcConnectorName, vpcConnectorName, vpcIp, bucketName, zipFilePath, functionName, vpcConnectorName) } -func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath string) string { +func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath, accountId string) string { return fmt.Sprintf(` data "google_project" "project" {} -resource "google_project_iam_member" "gcfadmin" { - project = data.google_project.project.project_id - role = "roles/editor" - member = "serviceAccount:service-%s@gcf-admin-robot.iam.gserviceaccount.com" -} - resource "google_service_account" "cloud_function_runner" { - account_id = "cloud-function-service" + account_id = "%s" display_name = "Testing Cloud Function Secrets integration" } @@ -1071,23 +1067,16 @@ resource "google_cloudfunctions_function" "function" { project_id = "%s" } - depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber, projectNumber) +`, accountId, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber, projectNumber) } -func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath string) string { +func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath, accountId string) string { return fmt.Sprintf(` data "google_project" "project" {} -resource "google_project_iam_member" "gcfadmin" { - project = data.google_project.project.project_id - role = "roles/editor" - member = "serviceAccount:service-%s@gcf-admin-robot.iam.gserviceaccount.com" -} - resource "google_service_account" "cloud_function_runner" { - account_id = "cloud-function-service" + account_id = "%s" display_name = "Testing Cloud Function Secrets integration" } @@ -1147,7 +1136,6 @@ resource "google_cloudfunctions_function" "function" { } } - depends_on = [google_project_iam_member.gcfadmin] } -`, projectNumber, secretName, versionName, bucketName, zipFilePath, functionName, projectNumber, versionNumber) +`, accountId, secretName, versionName, bucketName, zipFilePath, functionName, projectNumber, versionNumber) } \ No newline at end of file From 3f109b53f2cec2a1b4e32730a40ffbfcacefafb4 Mon Sep 17 00:00:00 2001 From: Bakh Inamov <292705+binamov@users.noreply.github.com> Date: Mon, 7 Feb 2022 11:20:59 -0800 Subject: [PATCH 07/11] project_id is computed, otherwise project number --- .../terraform/resources/resource_cloudfunctions_function.go | 2 ++ .../website/docs/r/cloudfunctions_function.html.markdown | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go index 0f824aa54352..199929d2175f 100644 --- a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go +++ b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go @@ -330,6 +330,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { "project_id": { Type: schema.TypeString, Optional: true, + Computed: true, Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, }, "secret": { @@ -360,6 +361,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { "project_id": { Type: schema.TypeString, Optional: true, + Computed: true, Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, }, "secret": { diff --git a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown index d05022d50ccb..5afb8abd136b 100644 --- a/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown +++ b/mmv1/third_party/terraform/website/docs/r/cloudfunctions_function.html.markdown @@ -183,7 +183,7 @@ which to observe events. For example, `"myBucket"` or `"projects/my-project/topi * `key` - (Required) Name of the environment variable. -* `project_id` - (Optional) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. +* `project_id` - (Optional) Project identifier (due to a known limitation, only project number is supported by this field) of the project that contains the secret. If not set, it will be populated with the function's project, assuming that the secret exists in the same project as of the function. * `secret` - (Required) ID of the secret in secret manager (not the full resource name). @@ -193,7 +193,7 @@ which to observe events. For example, `"myBucket"` or `"projects/my-project/topi * `mount_path` - (Required) The path within the container to mount the secret volume. For example, setting the mount_path as "/etc/secrets" would mount the secret value files under the "/etc/secrets" directory. This directory will also be completely shadowed and unavailable to mount any other secrets. Recommended mount paths: "/etc/secrets" Restricted mount paths: "/cloudsql", "/dev/log", "/pod", "/proc", "/var/log". -* `project_id` - (Optional) Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function. +* `project_id` - (Optional) Project identifier (due to a known limitation, only project number is supported by this field) of the project that contains the secret. If not set, it will be populated with the function's project, assuming that the secret exists in the same project as of the function. * `secret` - (Required) ID of the secret in secret manager (not the full resource name). From f9c9bf5491eb2c9eb0f934205bae89b2c22634ce Mon Sep 17 00:00:00 2001 From: Bakh Inamov <292705+binamov@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:04:25 -0800 Subject: [PATCH 08/11] test computedness of project_id for secret_env_vars --- .../tests/resource_cloudfunctions_function_test.go.erb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index f09072a2eff4..43dcc6cf64b2 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -444,7 +444,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { CheckDestroy: testAccCheckCloudFunctionsFunctionDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName1, bucketName, functionName, "1", zipFilePath, accountId), + Config: testAccCloudFunctionsFunction_secretEnvVar(secretName, versionName1, bucketName, functionName, "1", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -453,7 +453,7 @@ func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { ImportStateVerifyIgnore: []string{"build_environment_variables"}, }, { - Config: testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName2, bucketName +"-update", functionName, "2", zipFilePath, accountId), + Config: testAccCloudFunctionsFunction_secretEnvVar(secretName, versionName2, bucketName +"-update", functionName, "2", zipFilePath, accountId), }, { ResourceName: funcResourceName, @@ -1064,7 +1064,6 @@ resource "google_cloudfunctions_function" "function" { key = "MY_SECRET" secret = google_secret_manager_secret.test_secret.secret_id version = "%s" - project_id = "%s" } } From 258f6551f3bb2eb4543acdd3daf390d41057fb3a Mon Sep 17 00:00:00 2001 From: Bakh Inamov <292705+binamov@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:20:30 -0800 Subject: [PATCH 09/11] test computedness --- .../tests/resource_cloudfunctions_function_test.go.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index 43dcc6cf64b2..e50143ee34b7 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -1005,7 +1005,7 @@ resource "google_cloudfunctions_function" "function" { `, projectNumber, networkName, vpcConnectorName, vpcConnectorName, vpcIp, bucketName, zipFilePath, functionName, vpcConnectorName) } -func testAccCloudFunctionsFunction_secretEnvVar(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath, accountId string) string { +func testAccCloudFunctionsFunction_secretEnvVar(secretName, versionName, bucketName, functionName, versionNumber, zipFilePath, accountId string) string { return fmt.Sprintf(` data "google_project" "project" {} @@ -1067,7 +1067,7 @@ resource "google_cloudfunctions_function" "function" { } } -`, accountId, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber, projectNumber) +`, accountId, secretName, versionName, bucketName, zipFilePath, functionName, versionNumber) } func testAccCloudFunctionsFunction_secretMount(projectNumber, secretName, versionName, bucketName, functionName, versionNumber, zipFilePath, accountId string) string { From ec2d9339e43df09c60f76d2e93f9d552a1ef6072 Mon Sep 17 00:00:00 2001 From: Bakh Inamov <292705+binamov@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:44:35 -0800 Subject: [PATCH 10/11] removes unused declaration --- .../terraform/tests/resource_cloudfunctions_function_test.go.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb index e50143ee34b7..0c32e16470ae 100644 --- a/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb +++ b/mmv1/third_party/terraform/tests/resource_cloudfunctions_function_test.go.erb @@ -426,7 +426,6 @@ func TestAccCloudFunctionsFunction_vpcConnector(t *testing.T) { func TestAccCloudFunctionsFunction_secretEnvVar(t *testing.T) { t.Parallel() - projectNumber := os.Getenv("GOOGLE_PROJECT_NUMBER") randomSecretSuffix := randString(t, 10) accountId := fmt.Sprintf("tf-test-account-%s", randomSecretSuffix) secretName := fmt.Sprintf("tf-test-secret-%s", randomSecretSuffix) From a2aaaa9de0d129ebc2f023e0001457666a219e51 Mon Sep 17 00:00:00 2001 From: Ankit Garg Date: Mon, 7 Feb 2022 20:03:41 -0800 Subject: [PATCH 11/11] updating doc for project number. --- .../terraform/resources/resource_cloudfunctions_function.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go index 199929d2175f..319d1575862f 100644 --- a/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go +++ b/mmv1/third_party/terraform/resources/resource_cloudfunctions_function.go @@ -331,7 +331,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, + Description: `Project identifier (due to a known limitation, only project number is supported by this field) of the project that contains the secret. If not set, it will be populated with the function's project, assuming that the secret exists in the same project as of the function.`, }, "secret": { Type: schema.TypeString, @@ -362,7 +362,7 @@ func resourceCloudFunctionsFunction() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - Description: `Project identifier (preferably project number but can also be the project ID) of the project that contains the secret. If not set, it will be populated with the function's project assuming that the secret exists in the same project as of the function.`, + Description: `Project identifier (due to a known limitation, only project number is supported by this field) of the project that contains the secret. If not set, it will be populated with the function's project, assuming that the secret exists in the same project as of the function.`, }, "secret": { Type: schema.TypeString,