diff --git a/google/resource_pubsub_subscription.go b/google/resource_pubsub_subscription.go index d11ec56c788..21b05ddf812 100644 --- a/google/resource_pubsub_subscription.go +++ b/google/resource_pubsub_subscription.go @@ -113,6 +113,23 @@ func resourcePubsubSubscription() *schema.Resource { Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "oidc_token": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "service_account_email": { + Type: schema.TypeString, + Required: true, + }, + "audience": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, }, }, }, @@ -449,12 +466,37 @@ func flattenPubsubSubscriptionPushConfig(v interface{}, d *schema.ResourceData) return nil } transformed := make(map[string]interface{}) + transformed["oidc_token"] = + flattenPubsubSubscriptionPushConfigOidcToken(original["oidcToken"], d) transformed["push_endpoint"] = flattenPubsubSubscriptionPushConfigPushEndpoint(original["pushEndpoint"], d) transformed["attributes"] = flattenPubsubSubscriptionPushConfigAttributes(original["attributes"], d) return []interface{}{transformed} } +func flattenPubsubSubscriptionPushConfigOidcToken(v interface{}, d *schema.ResourceData) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["service_account_email"] = + flattenPubsubSubscriptionPushConfigOidcTokenServiceAccountEmail(original["serviceAccountEmail"], d) + transformed["audience"] = + flattenPubsubSubscriptionPushConfigOidcTokenAudience(original["audience"], d) + return []interface{}{transformed} +} +func flattenPubsubSubscriptionPushConfigOidcTokenServiceAccountEmail(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenPubsubSubscriptionPushConfigOidcTokenAudience(v interface{}, d *schema.ResourceData) interface{} { + return v +} + func flattenPubsubSubscriptionPushConfigPushEndpoint(v interface{}, d *schema.ResourceData) interface{} { return v } @@ -558,6 +600,13 @@ func expandPubsubSubscriptionPushConfig(v interface{}, d TerraformResourceData, original := raw.(map[string]interface{}) transformed := make(map[string]interface{}) + transformedOidcToken, err := expandPubsubSubscriptionPushConfigOidcToken(original["oidc_token"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOidcToken); val.IsValid() && !isEmptyValue(val) { + transformed["oidcToken"] = transformedOidcToken + } + transformedPushEndpoint, err := expandPubsubSubscriptionPushConfigPushEndpoint(original["push_endpoint"], d, config) if err != nil { return nil, err @@ -575,6 +624,40 @@ func expandPubsubSubscriptionPushConfig(v interface{}, d TerraformResourceData, return transformed, nil } +func expandPubsubSubscriptionPushConfigOidcToken(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedServiceAccountEmail, err := expandPubsubSubscriptionPushConfigOidcTokenServiceAccountEmail(original["service_account_email"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAccountEmail); val.IsValid() && !isEmptyValue(val) { + transformed["serviceAccountEmail"] = transformedServiceAccountEmail + } + + transformedAudience, err := expandPubsubSubscriptionPushConfigOidcTokenAudience(original["audience"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAudience); val.IsValid() && !isEmptyValue(val) { + transformed["audience"] = transformedAudience + } + + return transformed, nil +} + +func expandPubsubSubscriptionPushConfigOidcTokenServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandPubsubSubscriptionPushConfigOidcTokenAudience(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + func expandPubsubSubscriptionPushConfigPushEndpoint(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { return v, nil } diff --git a/google/resource_pubsub_subscription_test.go b/google/resource_pubsub_subscription_test.go index 55c379db8e1..8b9921381f5 100644 --- a/google/resource_pubsub_subscription_test.go +++ b/google/resource_pubsub_subscription_test.go @@ -105,6 +105,30 @@ func TestAccPubsubSubscription_update(t *testing.T) { }) } +func TestAccPubsubSubscription_push(t *testing.T) { + t.Parallel() + + topicFoo := fmt.Sprintf("tf-test-topic-foo-%s", acctest.RandString(10)) + subscription := fmt.Sprintf("projects/%s/subscriptions/tf-test-topic-foo-%s", getTestProjectFromEnv(), acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckPubsubSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPubsubSubscription_push(topicFoo, subscription), + }, + { + ResourceName: "google_pubsub_subscription.foo", + ImportStateId: subscription, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccPubsubSubscription_emptyTTL(topic, subscription string) string { return fmt.Sprintf(` resource "google_pubsub_topic" "foo" { @@ -123,18 +147,27 @@ resource "google_pubsub_subscription" "foo" { `, topic, subscription) } -// TODO: Add acceptance test for push delivery. -// -// Testing push endpoints is tricky for the following reason: -// - You need a publicly accessible HTTPS server to handle POST requests in order to receive push messages. -// - The server must present a valid SSL certificate signed by a certificate authority -// - The server must be routable by DNS. -// - You also need to validate that you own the domain (or have equivalent access to the endpoint). -// - Finally, you must register the endpoint domain with the GCP project. -// -// An easy way to test this would be to create an App Engine Hello World app. With AppEngine, SSL certificate, DNS and domain registry is handled for us. -// App Engine is not yet supported by Terraform but once it is, it will provide an easy path to testing push configs. -// Another option would be to use Cloud Functions once Terraform support is added. +func testAccPubsubSubscription_push(topicFoo string, subscription string) string { + return fmt.Sprintf(` +data "google_project" "project" {} + +resource "google_pubsub_topic" "foo" { + name = "%s" +} + +resource "google_pubsub_subscription" "foo" { + name = "%s" + topic = "${google_pubsub_topic.foo.name}" + ack_deadline_seconds = 10 + push_config { + push_endpoint = "https://${data.google_project.project.project_id}.appspot.com" + oidc_token { + service_account_email = "${google_service_account.service_account.email}" + } + } +} +`, topicFoo, subscription) +} func testAccPubsubSubscription_fullName(topic, subscription, label string, deadline int) string { return fmt.Sprintf(` diff --git a/website/docs/r/pubsub_subscription.html.markdown b/website/docs/r/pubsub_subscription.html.markdown index ab533c9d7eb..baa2065efe4 100644 --- a/website/docs/r/pubsub_subscription.html.markdown +++ b/website/docs/r/pubsub_subscription.html.markdown @@ -185,6 +185,11 @@ The following arguments are supported: The `push_config` block supports: +* `oidc_token` - + (Optional) + If specified, Pub/Sub will generate and attach an OIDC JWT token as + an Authorization header in the HTTP request for every pushed message. Structure is documented below. + * `push_endpoint` - (Required) A URL locating the endpoint to which messages should be pushed. @@ -212,6 +217,25 @@ The `push_config` block supports: - v1beta1: uses the push format defined in the v1beta1 Pub/Sub API. - v1 or v1beta2: uses the push format defined in the v1 Pub/Sub API. + +The `oidc_token` block supports: + +* `service_account_email` - + (Required) + Service account email to be used for generating the OIDC token. + The caller (for subscriptions.create, subscriptions.patch, and + subscriptions.modifyPushConfig RPCs) must have the + iam.serviceAccounts.actAs permission for the service account. + +* `audience` - + (Optional) + Audience to be used when generating OIDC token. The audience claim + identifies the recipients that the JWT is intended for. The audience + value is a single case-sensitive string. Having multiple values (array) + for the audience field is not supported. More info about the OIDC JWT + token audience here: https://tools.ietf.org/html/rfc7519#section-4.1.3 + Note: if not specified, the Push endpoint URL will be used. + The `expiration_policy` block supports: * `ttl` -