-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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 IAP support for backend services #471
Changes from all commits
ae5ad95
79df895
7b02cdb
b67c08e
e865ea2
285b10b
9de4d86
ec4afb2
95bb1c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just curious, why 1, 1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because this is called directly on the ResourceData object via .Set, which will always have at most one IAP object. I'm not sure why the IAP API has this as a list -- perhaps future expansion of IAP options and credentials? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, my question wasn't "why is this a list?", but "why is it being initialized with 1, 1 instead of 0, 1?" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, my apologies, I misread your question. Will adjust, :) |
||
} | ||
|
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eh, I don't necessarily mind having these checks here but they're not really what the test is testing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other tests do this too, even though it's not relevant (and I agree, I found it odd), so I decided to leave in for this test as well. |
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of a full testcheck here you could just use the existing one and then do checks on the returned svc like you're already doing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually prefer the testcheck personally versus the svc object test, as it feels more complete -- I actually would rather rip the svc checks out since they seem like they aren't really needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In what way is it more complete? Both ways would do the exact same thing, except one is duplicating a lot of code between functions and the other is calling an existing function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see your point. Will adjust. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I take it back. I started to implement the svc object check when I remembered why I don't like it, particularly for multi-stage tests. The svc object is ambiguous in both timing, and in scope. When is the svc object actually set? After destruction? Before? What about when you have multiple stages, which stage are we checking, IAP being set, or not? The answer may seem obvious, and it is (it's after all tests run), but it's not explicit and self contained. What we end up with is two different ways to validate a test run, and picking which way to test now follows a branching pattern (single stage? test svc. multi stage? test all but last stage with svc, and other stages with a check function). It makes more sense to me to follow a single pattern -- check each stage with a function and be explicit about intent. |
||
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}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you fix the formatting here? I think it's a tabs vs spaces issue There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
} | ||
|
||
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" { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will service.Iap ever be nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only in-so-far as the service object doesn't initialize it a few lines up from here. I'll add a check in the flatten function.