From bbaa23fa20b2069eb53772db9058ec68c02ac388 Mon Sep 17 00:00:00 2001 From: megan07 Date: Wed, 14 Aug 2019 15:02:53 +0000 Subject: [PATCH] Add support for oauth and oidc tokens to cloud_scheduler_job Signed-off-by: Modular Magician --- google/resource_cloud_scheduler_job.go | 225 ++++++++++++++++++ ...urce_cloud_scheduler_job_generated_test.go | 96 ++++++++ .../docs/r/cloud_scheduler_job.html.markdown | 89 +++++++ website/google.erb | 2 +- 4 files changed, 411 insertions(+), 1 deletion(-) diff --git a/google/resource_cloud_scheduler_job.go b/google/resource_cloud_scheduler_job.go index 69d2ab0d16a..3f2ec1bccd1 100644 --- a/google/resource_cloud_scheduler_job.go +++ b/google/resource_cloud_scheduler_job.go @@ -26,6 +26,55 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) +// Both oidc and oauth headers cannot be set +func validateAuthHeaders(diff *schema.ResourceDiff, v interface{}) error { + httpBlock := diff.Get("http_target.0").(map[string]interface{}) + + if httpBlock != nil { + oauth := httpBlock["oauth_token"] + oidc := httpBlock["oidc_token"] + + if oauth != nil && oidc != nil { + if len(oidc.([]interface{})) > 0 && len(oauth.([]interface{})) > 0 { + return fmt.Errorf("Error in http_target: only one of oauth_token or oidc_token can be specified, but not both.") + } + } + } + + return nil +} + +func authHeaderDiffSuppress(k, old, new string, d *schema.ResourceData) bool { + // If generating an `oauth_token` and `scope` is not provided in the configuration, + // the default "https://www.googleapis.com/auth/cloud-platform" scope will be used. + // Similarly, if generating an `oidc_token` and `audience` is not provided in the + // configuration, the URI specified in target will be used. Although not in the + // configuration, in both cases the default is returned in the object, but is not in. + // state. We suppress the diff if the values are these defaults but are not stored in state. + + b := strings.Split(k, ".") + if b[0] == "http_target" && len(b) > 4 { + block := b[2] + attr := b[4] + + if block == "oauth_token" && attr == "scope" { + if old == canonicalizeServiceScope("cloud-platform") && new == "" { + return true + } + } + + if block == "oidc_token" && attr == "audience" { + uri := d.Get(strings.Join(b[0:2], ".") + ".uri") + if old == uri && new == "" { + return true + } + } + + } + + return false +} + func resourceCloudSchedulerJob() *schema.Resource { return &schema.Resource{ Create: resourceCloudSchedulerJobCreate, @@ -41,6 +90,8 @@ func resourceCloudSchedulerJob() *schema.Resource { Delete: schema.DefaultTimeout(4 * time.Minute), }, + CustomizeDiff: validateAuthHeaders, + Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -145,6 +196,48 @@ func resourceCloudSchedulerJob() *schema.Resource { Optional: true, ForceNew: true, }, + "oauth_token": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: authHeaderDiffSuppress, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "scope": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "service_account_email": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "oidc_token": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + DiffSuppressFunc: authHeaderDiffSuppress, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "audience": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "service_account_email": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + }, }, }, ConflictsWith: []string{"pubsub_target", "app_engine_http_target"}, @@ -620,6 +713,10 @@ func flattenCloudSchedulerJobHttpTarget(v interface{}, d *schema.ResourceData) i flattenCloudSchedulerJobHttpTargetBody(original["body"], d) transformed["headers"] = flattenCloudSchedulerJobHttpTargetHeaders(original["headers"], d) + transformed["oauth_token"] = + flattenCloudSchedulerJobHttpTargetOauthToken(original["oauthToken"], d) + transformed["oidc_token"] = + flattenCloudSchedulerJobHttpTargetOidcToken(original["oidcToken"], d) return []interface{}{transformed} } func flattenCloudSchedulerJobHttpTargetUri(v interface{}, d *schema.ResourceData) interface{} { @@ -659,6 +756,52 @@ func flattenCloudSchedulerJobHttpTargetHeaders(v interface{}, d *schema.Resource return headers } +func flattenCloudSchedulerJobHttpTargetOauthToken(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"] = + flattenCloudSchedulerJobHttpTargetOauthTokenServiceAccountEmail(original["serviceAccountEmail"], d) + transformed["scope"] = + flattenCloudSchedulerJobHttpTargetOauthTokenScope(original["scope"], d) + return []interface{}{transformed} +} +func flattenCloudSchedulerJobHttpTargetOauthTokenServiceAccountEmail(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudSchedulerJobHttpTargetOauthTokenScope(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudSchedulerJobHttpTargetOidcToken(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"] = + flattenCloudSchedulerJobHttpTargetOidcTokenServiceAccountEmail(original["serviceAccountEmail"], d) + transformed["audience"] = + flattenCloudSchedulerJobHttpTargetOidcTokenAudience(original["audience"], d) + return []interface{}{transformed} +} +func flattenCloudSchedulerJobHttpTargetOidcTokenServiceAccountEmail(v interface{}, d *schema.ResourceData) interface{} { + return v +} + +func flattenCloudSchedulerJobHttpTargetOidcTokenAudience(v interface{}, d *schema.ResourceData) interface{} { + return v +} + func expandCloudSchedulerJobName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { var jobName string project, err := getProject(d, config) @@ -964,6 +1107,20 @@ func expandCloudSchedulerJobHttpTarget(v interface{}, d TerraformResourceData, c transformed["headers"] = transformedHeaders } + transformedOauthToken, err := expandCloudSchedulerJobHttpTargetOauthToken(original["oauth_token"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOauthToken); val.IsValid() && !isEmptyValue(val) { + transformed["oauthToken"] = transformedOauthToken + } + + transformedOidcToken, err := expandCloudSchedulerJobHttpTargetOidcToken(original["oidc_token"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOidcToken); val.IsValid() && !isEmptyValue(val) { + transformed["oidcToken"] = transformedOidcToken + } + return transformed, nil } @@ -989,3 +1146,71 @@ func expandCloudSchedulerJobHttpTargetHeaders(v interface{}, d TerraformResource } return m, nil } + +func expandCloudSchedulerJobHttpTargetOauthToken(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 := expandCloudSchedulerJobHttpTargetOauthTokenServiceAccountEmail(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 + } + + transformedScope, err := expandCloudSchedulerJobHttpTargetOauthTokenScope(original["scope"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedScope); val.IsValid() && !isEmptyValue(val) { + transformed["scope"] = transformedScope + } + + return transformed, nil +} + +func expandCloudSchedulerJobHttpTargetOauthTokenServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudSchedulerJobHttpTargetOauthTokenScope(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudSchedulerJobHttpTargetOidcToken(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 := expandCloudSchedulerJobHttpTargetOidcTokenServiceAccountEmail(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 := expandCloudSchedulerJobHttpTargetOidcTokenAudience(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 expandCloudSchedulerJobHttpTargetOidcTokenServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandCloudSchedulerJobHttpTargetOidcTokenAudience(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_cloud_scheduler_job_generated_test.go b/google/resource_cloud_scheduler_job_generated_test.go index b1c78b55b66..cc631452ee9 100644 --- a/google/resource_cloud_scheduler_job_generated_test.go +++ b/google/resource_cloud_scheduler_job_generated_test.go @@ -157,6 +157,102 @@ resource "google_cloud_scheduler_job" "job" { `, context) } +func TestAccCloudSchedulerJob_schedulerJobOauthExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project_name": getTestProjectFromEnv(), + "region": getTestRegionFromEnv(), + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSchedulerJob_schedulerJobOauthExample(context), + }, + { + ResourceName: "google_cloud_scheduler_job.job", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"region"}, + }, + }, + }) +} + +func testAccCloudSchedulerJob_schedulerJobOauthExample(context map[string]interface{}) string { + return Nprintf(` +data "google_compute_default_service_account" "default" { } + +resource "google_cloud_scheduler_job" "job" { + name = "test-job%{random_suffix}" + description = "test http job" + schedule = "*/8 * * * *" + time_zone = "America/New_York" + + http_target { + http_method = "GET" + uri = "https://cloudscheduler.googleapis.com/v1/projects/%{project_name}/locations/%{region}/jobs" + + oauth_token { + service_account_email = "${data.google_compute_default_service_account.default.email}" + } + } +} +`, context) +} + +func TestAccCloudSchedulerJob_schedulerJobOidcExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(10), + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudSchedulerJobDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudSchedulerJob_schedulerJobOidcExample(context), + }, + { + ResourceName: "google_cloud_scheduler_job.job", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"region"}, + }, + }, + }) +} + +func testAccCloudSchedulerJob_schedulerJobOidcExample(context map[string]interface{}) string { + return Nprintf(` +data "google_compute_default_service_account" "default" { } + +resource "google_cloud_scheduler_job" "job" { + name = "test-job%{random_suffix}" + description = "test http job" + schedule = "*/8 * * * *" + time_zone = "America/New_York" + + http_target { + http_method = "GET" + uri = "https://example.com/ping" + + oidc_token { + service_account_email = "${data.google_compute_default_service_account.default.email}" + } + } +} +`, context) +} + func testAccCheckCloudSchedulerJobDestroy(s *terraform.State) error { for name, rs := range s.RootModule().Resources { if rs.Type != "google_cloud_scheduler_job" { diff --git a/website/docs/r/cloud_scheduler_job.html.markdown b/website/docs/r/cloud_scheduler_job.html.markdown index c5154da83b8..171a4313169 100644 --- a/website/docs/r/cloud_scheduler_job.html.markdown +++ b/website/docs/r/cloud_scheduler_job.html.markdown @@ -109,6 +109,60 @@ resource "google_cloud_scheduler_job" "job" { } } ``` + +## Example Usage - Scheduler Job Oauth + + +```hcl +data "google_compute_default_service_account" "default" { } + +resource "google_cloud_scheduler_job" "job" { + name = "test-job" + description = "test http job" + schedule = "*/8 * * * *" + time_zone = "America/New_York" + + http_target { + http_method = "GET" + uri = "https://cloudscheduler.googleapis.com/v1/projects/my-project-name/locations/us-west1/jobs" + + oauth_token { + service_account_email = "${data.google_compute_default_service_account.default.email}" + } + } +} +``` + +## Example Usage - Scheduler Job Oidc + + +```hcl +data "google_compute_default_service_account" "default" { } + +resource "google_cloud_scheduler_job" "job" { + name = "test-job" + description = "test http job" + schedule = "*/8 * * * *" + time_zone = "America/New_York" + + http_target { + http_method = "GET" + uri = "https://example.com/ping" + + oidc_token { + service_account_email = "${data.google_compute_default_service_account.default.email}" + } + } +} +``` ## Argument Reference @@ -287,6 +341,41 @@ The `http_target` block supports: This map contains the header field names and values. Repeated headers are not supported, but a header value can contain commas. +* `oauth_token` - + (Optional) + Contains information needed for generating an OAuth token. + This type of authorization should be used when sending requests to a GCP endpoint. Structure is documented below. + +* `oidc_token` - + (Optional) + Contains information needed for generating an OpenID Connect token. + This type of authorization should be used when sending requests to third party endpoints or Cloud Run. Structure is documented below. + + +The `oauth_token` block supports: + +* `service_account_email` - + (Optional) + Service account email to be used for generating OAuth token. + The service account must be within the same project as the job. + +* `scope` - + (Optional) + OAuth scope to be used for generating OAuth access token. If not specified, + "https://www.googleapis.com/auth/cloud-platform" will be used. + +The `oidc_token` block supports: + +* `service_account_email` - + (Optional) + Service account email to be used for generating OAuth token. + The service account must be within the same project as the job. + +* `audience` - + (Optional) + Audience to be used when generating OIDC token. If not specified, + the URI specified in target will be used. + ## Timeouts diff --git a/website/google.erb b/website/google.erb index bd3b7906ac4..19b05ac555d 100644 --- a/website/google.erb +++ b/website/google.erb @@ -670,7 +670,7 @@ > - google_dataproc_job + google_dataproc_job >