diff --git a/google/resource_compute_backend_service.go b/google/resource_compute_backend_service.go index fc59f55138f..f583ad10e69 100644 --- a/google/resource_compute_backend_service.go +++ b/google/resource_compute_backend_service.go @@ -2,6 +2,7 @@ package google import ( "bytes" + "crypto/sha256" "errors" "fmt" "log" @@ -39,8 +40,35 @@ func resourceComputeBackendService() *schema.Resource { MaxItems: 1, }, + "iap": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "oauth2_client_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "oauth2_client_secret": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Sensitive: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if old == fmt.Sprintf("%x", sha256.Sum256([]byte(new))) { + return true + } + return false + }, + }, + }, + }, + }, + "backend": &schema.Schema{ - Type: schema.TypeSet, + Type: schema.TypeSet, + Optional: true, + Set: resourceGoogleComputeBackendServiceBackendHash, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "group": &schema.Schema{ @@ -77,8 +105,6 @@ func resourceComputeBackendService() *schema.Resource { }, }, }, - Optional: true, - Set: resourceGoogleComputeBackendServiceBackendHash, }, "description": &schema.Schema{ @@ -209,6 +235,7 @@ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{}) d.Set("self_link", service.SelfLink) d.Set("backend", flattenBackends(service.Backends)) d.Set("connection_draining_timeout_sec", service.ConnectionDraining.DrainingTimeoutSec) + d.Set("iap", flattenIap(service.Iap)) d.Set("health_checks", service.HealthChecks) @@ -270,6 +297,31 @@ func resourceComputeBackendServiceDelete(d *schema.ResourceData, meta interface{ return nil } +func expandIap(configured []interface{}) *compute.BackendServiceIAP { + data := configured[0].(map[string]interface{}) + iap := &compute.BackendServiceIAP{ + Enabled: true, + Oauth2ClientId: data["oauth2_client_id"].(string), + Oauth2ClientSecret: data["oauth2_client_secret"].(string), + ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"}, + } + + return iap +} + +func flattenIap(iap *compute.BackendServiceIAP) []map[string]interface{} { + if iap == nil { + return make([]map[string]interface{}, 1, 1) + } + + iapMap := map[string]interface{}{ + "enabled": iap.Enabled, + "oauth2_client_id": iap.Oauth2ClientId, + "oauth2_client_secret": iap.Oauth2ClientSecretSha256, + } + return []map[string]interface{}{iapMap} +} + func expandBackends(configured []interface{}) ([]*compute.Backend, error) { backends := make([]*compute.Backend, 0, len(configured)) @@ -323,7 +375,6 @@ func flattenBackends(backends []*compute.Backend) []map[string]interface{} { data["max_rate"] = b.MaxRate data["max_rate_per_instance"] = b.MaxRatePerInstance data["max_utilization"] = b.MaxUtilization - result = append(result, data) } @@ -337,9 +388,23 @@ func expandBackendService(d *schema.ResourceData) (*compute.BackendService, erro healthChecks = append(healthChecks, v.(string)) } + // The IAP service is enabled and disabled by adding or removing + // the IAP configuration block (and providing the client id + // and secret). We are force sending the three required API fields + // to enable/disable IAP at all times here, and relying on Golang's + // type defaults to enable or disable IAP in the existance or absense + // of the block, instead of checking if the block exists, zeroing out + // fields, etc. service := &compute.BackendService{ Name: d.Get("name").(string), HealthChecks: healthChecks, + Iap: &compute.BackendServiceIAP{ + ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"}, + }, + } + + if v, ok := d.GetOk("iap"); ok { + service.Iap = expandIap(v.([]interface{})) } var err error diff --git a/google/resource_compute_backend_service_test.go b/google/resource_compute_backend_service_test.go index 219f8bf5067..663442c708e 100644 --- a/google/resource_compute_backend_service_test.go +++ b/google/resource_compute_backend_service_test.go @@ -120,6 +120,48 @@ func TestAccComputeBackendService_withBackendAndUpdate(t *testing.T) { } } +func TestAccComputeBackendService_withBackendAndIAP(t *testing.T) { + serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10)) + var svc compute.BackendService + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeBackendServiceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccComputeBackendService_withBackendAndIAP( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExistsWithIAP( + "google_compute_backend_service.lipsum", &svc), + ), + }, + resource.TestStep{ + Config: testAccComputeBackendService_withBackend( + serviceName, igName, itName, checkName, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckComputeBackendServiceExistsWithoutIAP( + "google_compute_backend_service.lipsum", &svc), + ), + }, + }, + }) + + if svc.TimeoutSec != 10 { + t.Errorf("Expected TimeoutSec == 10, got %d", svc.TimeoutSec) + } + if svc.Protocol != "HTTP" { + t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol) + } + if len(svc.Backends) != 1 { + t.Errorf("Expected 1 backend, got %d", len(svc.Backends)) + } + +} + func TestAccComputeBackendService_updatePreservesOptionalParameters(t *testing.T) { t.Parallel() @@ -287,6 +329,71 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi } } +func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.BackendService) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service %s not found", rs.Primary.ID) + } + + if found.Iap == nil || found.Iap.Enabled == false { + return fmt.Errorf("IAP not found or not enabled.") + } + + *svc = *found + + return nil + } +} + +func testAccCheckComputeBackendServiceExistsWithoutIAP(n string, svc *compute.BackendService) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + config := testAccProvider.Meta().(*Config) + + found, err := config.clientCompute.BackendServices.Get( + config.Project, rs.Primary.ID).Do() + if err != nil { + return err + } + + if found.Name != rs.Primary.ID { + return fmt.Errorf("Backend service %s not found", rs.Primary.ID) + } + + if found.Iap != nil && found.Iap.Enabled == true { + return fmt.Errorf("IAP enabled when it should be disabled") + } + + *svc = *found + + return nil + } +} func TestAccComputeBackendService_withCDNEnabled(t *testing.T) { t.Parallel() @@ -460,6 +567,60 @@ resource "google_compute_http_health_check" "default" { `, serviceName, timeout, igName, itName, checkName) } +func testAccComputeBackendService_withBackendAndIAP( + serviceName, igName, itName, checkName string, timeout int64) string { + return fmt.Sprintf(` +resource "google_compute_backend_service" "lipsum" { + name = "%s" + description = "Hello World 1234" + port_name = "http" + protocol = "HTTP" + timeout_sec = %v + + backend { + group = "${google_compute_instance_group_manager.foobar.instance_group}" + } + + iap { + oauth2_client_id = "test" + oauth2_client_secret = "test" + } + + health_checks = ["${google_compute_http_health_check.default.self_link}"] +} + +resource "google_compute_instance_group_manager" "foobar" { + name = "%s" + instance_template = "${google_compute_instance_template.foobar.self_link}" + base_instance_name = "foobar" + zone = "us-central1-f" + target_size = 1 +} + +resource "google_compute_instance_template" "foobar" { + name = "%s" + machine_type = "n1-standard-1" + + network_interface { + network = "default" + } + + disk { + source_image = "debian-8-jessie-v20160803" + auto_delete = true + boot = true + } +} + +resource "google_compute_http_health_check" "default" { + name = "%s" + request_path = "/" + check_interval_sec = 1 + timeout_sec = 1 +} +`, serviceName, timeout, igName, itName, checkName) +} + func testAccComputeBackendService_withSessionAffinity(serviceName, checkName, description, affinityName string) string { return fmt.Sprintf(` resource "google_compute_backend_service" "foobar" {