From 9077963feee0fca3b4a304925815813de6e3e607 Mon Sep 17 00:00:00 2001 From: Roberto Jung Drebes Date: Tue, 25 Jun 2019 23:55:39 +0000 Subject: [PATCH] Pub/Sub Topic CMEK/KMS support Signed-off-by: Modular Magician --- google/resource_pubsub_topic.go | 22 ++++ google/resource_pubsub_topic_test.go | 126 ++++++++++++++++++++++ website/docs/r/pubsub_topic.html.markdown | 25 +++++ 3 files changed, 173 insertions(+) diff --git a/google/resource_pubsub_topic.go b/google/resource_pubsub_topic.go index 854e4955798..46d0351a795 100644 --- a/google/resource_pubsub_topic.go +++ b/google/resource_pubsub_topic.go @@ -48,6 +48,11 @@ func resourcePubsubTopic() *schema.Resource { ForceNew: true, DiffSuppressFunc: compareSelfLinkOrResourceName, }, + "kms_key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, "labels": { Type: schema.TypeMap, Optional: true, @@ -73,6 +78,12 @@ func resourcePubsubTopicCreate(d *schema.ResourceData, meta interface{}) error { } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { obj["name"] = nameProp } + kmsKeyNameProp, err := expandPubsubTopicKmsKeyName(d.Get("kms_key_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("kms_key_name"); !isEmptyValue(reflect.ValueOf(kmsKeyNameProp)) && (ok || !reflect.DeepEqual(v, kmsKeyNameProp)) { + obj["kmsKeyName"] = kmsKeyNameProp + } labelsProp, err := expandPubsubTopicLabels(d.Get("labels"), d, config) if err != nil { return err @@ -132,6 +143,9 @@ func resourcePubsubTopicRead(d *schema.ResourceData, meta interface{}) error { if err := d.Set("name", flattenPubsubTopicName(res["name"], d)); err != nil { return fmt.Errorf("Error reading Topic: %s", err) } + if err := d.Set("kms_key_name", flattenPubsubTopicKmsKeyName(res["kmsKeyName"], d)); err != nil { + return fmt.Errorf("Error reading Topic: %s", err) + } if err := d.Set("labels", flattenPubsubTopicLabels(res["labels"], d)); err != nil { return fmt.Errorf("Error reading Topic: %s", err) } @@ -223,6 +237,10 @@ func flattenPubsubTopicName(v interface{}, d *schema.ResourceData) interface{} { return NameFromSelfLinkStateFunc(v) } +func flattenPubsubTopicKmsKeyName(v interface{}, d *schema.ResourceData) interface{} { + return v +} + func flattenPubsubTopicLabels(v interface{}, d *schema.ResourceData) interface{} { return v } @@ -231,6 +249,10 @@ func expandPubsubTopicName(v interface{}, d TerraformResourceData, config *Confi return GetResourceNameFromSelfLink(v.(string)), nil } +func expandPubsubTopicKmsKeyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandPubsubTopicLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { if v == nil { return map[string]string{}, nil diff --git a/google/resource_pubsub_topic_test.go b/google/resource_pubsub_topic_test.go index 56e6ca83c62..a235aa98fd7 100644 --- a/google/resource_pubsub_topic_test.go +++ b/google/resource_pubsub_topic_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" ) func TestAccPubsubTopic_update(t *testing.T) { @@ -40,6 +41,37 @@ func TestAccPubsubTopic_update(t *testing.T) { }) } +func TestAccPubsubTopic_cmek(t *testing.T) { + t.Parallel() + + projectId := "terraform-" + acctest.RandString(10) + projectOrg := getTestOrgFromEnv(t) + projectBillingAccount := getTestBillingAccountFromEnv(t) + keyRingName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + cryptoKeyName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + topicName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccPubsubTopic_cmek(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, topicName), + }, + { + ResourceName: "google_pubsub_topic.topic", + ImportState: true, + ImportStateVerify: true, + }, + // Use a separate TestStep rather than a CheckDestroy because we need the project to still exist. + { + Config: testAccPubsubTopic_removed(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName), + Check: testAccCheckPubsubTopicWasRemovedFromState("google_pubsub_topic.topic"), + }, + }, + }) +} + func testAccPubsubTopic_update(topic, key, value string) string { return fmt.Sprintf(` resource "google_pubsub_topic" "foo" { @@ -50,3 +82,97 @@ resource "google_pubsub_topic" "foo" { } `, topic, key, value) } + +// This test runs in its own project, otherwise the test project would start to get filled +// with undeletable resources +func testAccPubsubTopic_cmek(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, topicName string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudkms.googleapis.com", + "pubsub.googleapis.com", + ] +} + +resource "google_kms_key_ring" "key_ring" { + project = "${google_project_services.acceptance.project}" + name = "%s" + location = "global" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "%s" + key_ring = "${google_kms_key_ring.key_ring.self_link}" +} + +resource "google_project_iam_member" "svc-acct" { + project = "${google_project_services.acceptance.project}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${google_project.acceptance.number}@gcp-sa-pubsub.iam.gserviceaccount.com" +} + +resource "google_pubsub_topic" "topic" { + name = "%s" + project = "${google_project_iam_member.svc-acct.project}" + kms_key_name = "${google_kms_crypto_key.crypto_key.self_link}" +} +`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName, topicName) +} + +func testAccPubsubTopic_removed(projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName string) string { + return fmt.Sprintf(` +resource "google_project" "acceptance" { + name = "%s" + project_id = "%s" + org_id = "%s" + billing_account = "%s" +} + +resource "google_project_services" "acceptance" { + project = "${google_project.acceptance.project_id}" + + services = [ + "cloudkms.googleapis.com", + "pubsub.googleapis.com", + ] +} + +resource "google_kms_key_ring" "key_ring" { + project = "${google_project_services.acceptance.project}" + name = "%s" + location = "global" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "%s" + key_ring = "${google_kms_key_ring.key_ring.self_link}" +} + +resource "google_project_iam_member" "svc-acct" { + project = "${google_project_services.acceptance.project}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${google_project.acceptance.number}@gcp-sa-pubsub.iam.gserviceaccount.com" +} +`, projectId, projectId, projectOrg, projectBillingAccount, keyRingName, cryptoKeyName) +} + +func testAccCheckPubsubTopicWasRemovedFromState(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[resourceName] + + if ok { + return fmt.Errorf("Resource was not removed from state: %s", resourceName) + } + + return nil + } +} diff --git a/website/docs/r/pubsub_topic.html.markdown b/website/docs/r/pubsub_topic.html.markdown index d9c5e1892bb..59eb02a0ba8 100644 --- a/website/docs/r/pubsub_topic.html.markdown +++ b/website/docs/r/pubsub_topic.html.markdown @@ -47,6 +47,25 @@ resource "google_pubsub_topic" "example" { } } ``` +## Example Usage - Pubsub Topic Cmek + + +```hcl +resource "google_pubsub_topic" "example" { + name = "example-topic" + kms_key_name = "${google_kms_crypto_key.crypto_key.self_link}" +} + +resource "google_kms_crypto_key" "crypto_key" { + name = "example-key" + key_ring = "${google_kms_key_ring.key_ring.self_link}" +} + +resource "google_kms_key_ring" "key_ring" { + name = "example-keyring" + location = "global" +} +``` ## Argument Reference @@ -61,6 +80,12 @@ The following arguments are supported: - - - +* `kms_key_name` - + (Optional) + The resource name of the Cloud KMS CryptoKey to be used to protect access + to messsages published on this topic. + The expected format is `projects/*/locations/*/keyRings/*/cryptoKeys/*` + * `labels` - (Optional) A set of key/value label pairs to assign to this Topic.