Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GCF Gen2 CMEK #6004

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/8518.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
cloudfunctionsv2: added support for GCF Gen2 CMEK
```
147 changes: 147 additions & 0 deletions google-beta/resource_cloudfunctions2_function_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,153 @@ resource "google_cloudfunctions2_function" "function" {
`, context)
}

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

context := map[string]interface{}{
"project": envvar.GetTestProjectFromEnv(),
"kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name,
"zip_path": "./test-fixtures/cloudfunctions2/function-source.zip",
"location": "us-central1",
"random_suffix": acctest.RandString(t, 10),
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t),
CheckDestroy: testAccCheckCloudfunctions2functionDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccCloudfunctions2function_cloudfunctions2CmekExample(context),
},
{
ResourceName: "google_cloudfunctions2_function.function",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"location", "build_config.0.source.0.storage_source.0.object", "build_config.0.source.0.storage_source.0.bucket"},
},
},
})
}

func testAccCloudfunctions2function_cloudfunctions2CmekExample(context map[string]interface{}) string {
return acctest.Nprintf(`
locals {
project = "%{project}" # Google Cloud Platform Project ID
}

data "google_project" "project" {
provider = google-beta
}

resource "google_storage_bucket" "bucket" {
provider = google-beta

name = "${local.project}-tf-test-gcf-source%{random_suffix}" # Every bucket name must be globally unique
location = "US"
uniform_bucket_level_access = true
}

resource "google_storage_bucket_object" "object" {
provider = google-beta

name = "function-source.zip"
bucket = google_storage_bucket.bucket.name
source = "%{zip_path}" # Add path to the zipped function source code
}

resource "google_project_service_identity" "ea_sa" {
provider = google-beta

project = data.google_project.project.project_id
service = "eventarc.googleapis.com"
}

resource "google_artifact_registry_repository" "unencoded-ar-repo" {
provider = google-beta

repository_id = "tf-test-ar-repo%{random_suffix}"
location = "us-central1"
format = "DOCKER"
}

resource "google_artifact_registry_repository_iam_binding" "binding" {
provider = google-beta

location = google_artifact_registry_repository.encoded-ar-repo.location
repository = google_artifact_registry_repository.encoded-ar-repo.name
role = "roles/artifactregistry.admin"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
]
}

resource "google_kms_crypto_key_iam_binding" "gcf_cmek_keyuser" {
provider = google-beta

crypto_key_id = "%{kms_key_name}"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"

members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gs-project-accounts.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@serverless-robot-prod.iam.gserviceaccount.com",
"serviceAccount:${google_project_service_identity.ea_sa.email}",
]

depends_on = [
google_project_service_identity.ea_sa
]
}

resource "google_artifact_registry_repository" "encoded-ar-repo" {
provider = google-beta

location = "us-central1"
repository_id = "tf-test-cmek-repo%{random_suffix}"
format = "DOCKER"
kms_key_name = "%{kms_key_name}"
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}

resource "google_cloudfunctions2_function" "function" {
provider = google-beta

name = "tf-test-function-cmek%{random_suffix}"
location = "us-central1"
description = "CMEK function"
kms_key_name = "%{kms_key_name}"

build_config {
runtime = "nodejs16"
entry_point = "helloHttp" # Set the entry point
docker_repository = google_artifact_registry_repository.encoded-ar-repo.id

source {
storage_source {
bucket = google_storage_bucket.bucket.name
object = google_storage_bucket_object.object.name
}
}
}

service_config {
max_instance_count = 1
available_memory = "256M"
timeout_seconds = 60
}

depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]

}
`, context)
}

func testAccCheckCloudfunctions2functionDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,12 @@ region. If not provided, defaults to the same region as the function.`,
},
},
},
"kms_key_name": {
Type: schema.TypeString,
Optional: true,
Description: `Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources.
It must match the pattern projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}.`,
},
"labels": {
Type: schema.TypeMap,
Optional: true,
Expand Down Expand Up @@ -548,6 +554,12 @@ func resourceCloudfunctions2functionCreate(d *schema.ResourceData, meta interfac
} else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
obj["labels"] = labelsProp
}
kmsKeyNameProp, err := expandCloudfunctions2functionKmsKeyName(d.Get("kms_key_name"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("kms_key_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(kmsKeyNameProp)) && (ok || !reflect.DeepEqual(v, kmsKeyNameProp)) {
obj["kmsKeyName"] = kmsKeyNameProp
}

url, err := tpgresource.ReplaceVars(d, config, "{{Cloudfunctions2BasePath}}projects/{{project}}/locations/{{location}}/functions?functionId={{name}}")
if err != nil {
Expand Down Expand Up @@ -687,6 +699,9 @@ func resourceCloudfunctions2functionRead(d *schema.ResourceData, meta interface{
if err := d.Set("labels", flattenCloudfunctions2functionLabels(res["labels"], d, config)); err != nil {
return fmt.Errorf("Error reading function: %s", err)
}
if err := d.Set("kms_key_name", flattenCloudfunctions2functionKmsKeyName(res["kmsKeyName"], d, config)); err != nil {
return fmt.Errorf("Error reading function: %s", err)
}

return nil
}
Expand Down Expand Up @@ -737,6 +752,12 @@ func resourceCloudfunctions2functionUpdate(d *schema.ResourceData, meta interfac
} else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
obj["labels"] = labelsProp
}
kmsKeyNameProp, err := expandCloudfunctions2functionKmsKeyName(d.Get("kms_key_name"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("kms_key_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, kmsKeyNameProp)) {
obj["kmsKeyName"] = kmsKeyNameProp
}

url, err := tpgresource.ReplaceVars(d, config, "{{Cloudfunctions2BasePath}}projects/{{project}}/locations/{{location}}/functions/{{name}}")
if err != nil {
Expand Down Expand Up @@ -765,6 +786,10 @@ func resourceCloudfunctions2functionUpdate(d *schema.ResourceData, meta interfac
if d.HasChange("labels") {
updateMask = append(updateMask, "labels")
}

if d.HasChange("kms_key_name") {
updateMask = append(updateMask, "kmsKeyName")
}
// updateMask is a URL parameter but not present in the schema, so ReplaceVars
// won't set it
url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")})
Expand Down Expand Up @@ -1429,6 +1454,10 @@ func flattenCloudfunctions2functionLabels(v interface{}, d *schema.ResourceData,
return v
}

func flattenCloudfunctions2functionKmsKeyName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
return v
}

func expandCloudfunctions2functionName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/functions/{{name}}")
}
Expand Down Expand Up @@ -2182,3 +2211,7 @@ func expandCloudfunctions2functionLabels(v interface{}, d tpgresource.TerraformR
}
return m, nil
}

func expandCloudfunctions2functionKmsKeyName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
return v, nil
}
123 changes: 123 additions & 0 deletions website/docs/r/cloudfunctions2_function.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,124 @@ resource "google_cloudfunctions2_function" "function" {
}
}
```
## Example Usage - Cloudfunctions2 Cmek


```hcl
locals {
project = "my-project-name" # Google Cloud Platform Project ID
}

data "google_project" "project" {
provider = google-beta
}

resource "google_storage_bucket" "bucket" {
provider = google-beta

name = "${local.project}-gcf-source" # Every bucket name must be globally unique
location = "US"
uniform_bucket_level_access = true
}

resource "google_storage_bucket_object" "object" {
provider = google-beta

name = "function-source.zip"
bucket = google_storage_bucket.bucket.name
source = "function-source.zip" # Add path to the zipped function source code
}

resource "google_project_service_identity" "ea_sa" {
provider = google-beta

project = data.google_project.project.project_id
service = "eventarc.googleapis.com"
}

resource "google_artifact_registry_repository" "unencoded-ar-repo" {
provider = google-beta

repository_id = "ar-repo"
location = "us-central1"
format = "DOCKER"
}

resource "google_artifact_registry_repository_iam_binding" "binding" {
provider = google-beta

location = google_artifact_registry_repository.encoded-ar-repo.location
repository = google_artifact_registry_repository.encoded-ar-repo.name
role = "roles/artifactregistry.admin"
members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
]
}

resource "google_kms_crypto_key_iam_binding" "gcf_cmek_keyuser" {
provider = google-beta

crypto_key_id = "cmek-key"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"

members = [
"serviceAccount:service-${data.google_project.project.number}@gcf-admin-robot.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gcp-sa-artifactregistry.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@gs-project-accounts.iam.gserviceaccount.com",
"serviceAccount:service-${data.google_project.project.number}@serverless-robot-prod.iam.gserviceaccount.com",
"serviceAccount:${google_project_service_identity.ea_sa.email}",
]

depends_on = [
google_project_service_identity.ea_sa
]
}

resource "google_artifact_registry_repository" "encoded-ar-repo" {
provider = google-beta

location = "us-central1"
repository_id = "cmek-repo"
format = "DOCKER"
kms_key_name = "cmek-key"
depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]
}

resource "google_cloudfunctions2_function" "function" {
provider = google-beta

name = "function-cmek"
location = "us-central1"
description = "CMEK function"
kms_key_name = "cmek-key"

build_config {
runtime = "nodejs16"
entry_point = "helloHttp" # Set the entry point
docker_repository = google_artifact_registry_repository.encoded-ar-repo.id

source {
storage_source {
bucket = google_storage_bucket.bucket.name
object = google_storage_bucket_object.object.name
}
}
}

service_config {
max_instance_count = 1
available_memory = "256M"
timeout_seconds = 60
}

depends_on = [
google_kms_crypto_key_iam_binding.gcf_cmek_keyuser
]

}
```

## Argument Reference

Expand Down Expand Up @@ -680,6 +798,11 @@ The following arguments are supported:
(Optional)
A set of key/value label pairs associated with this Cloud Function.

* `kms_key_name` -
(Optional)
Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources.
It must match the pattern projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}.

* `location` -
(Optional)
The location of this cloud function.
Expand Down