Skip to content
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

Merged
merged 9 commits into from
Oct 31, 2017
Merged

Conversation

Cidan
Copy link
Contributor

@Cidan Cidan commented Sep 27, 2017

WIP PR, do not merge yet

Adds IAP support for #444

@Cidan
Copy link
Contributor Author

Cidan commented Sep 27, 2017

I think this is more or less good to go, though I might be missing something. @danawillow, whatcha think?

@Cidan
Copy link
Contributor Author

Cidan commented Sep 27, 2017

sorry, one last change, wrote a reciprocal function for flatten to keep in line with previous work. now it should be good!

@danawillow danawillow self-requested a review September 27, 2017 16:54
@danawillow danawillow self-assigned this Sep 27, 2017
Copy link
Contributor

@danawillow danawillow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Cidan! Can you add tests in resource_compute_backend_service_test.go? I think a test with one step that sets up the backend service with IAP enabled and another that disables IAP would be good.

@@ -37,6 +37,32 @@ func resourceComputeBackendService() *schema.Resource {
MaxItems: 1,
},

"iap": &schema.Schema{
Type: schema.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's MaxItems: 1 anyway, I'd recommend just making this a list. Then you can also get rid of the Set fn on line 63

@@ -37,6 +37,32 @@ func resourceComputeBackendService() *schema.Resource {
MaxItems: 1,
},

"iap": &schema.Schema{
Type: schema.TypeSet,
Elem: &schema.Resource{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of our other resources have Elem as the last item in the struct. I'm not sure why this resource does it the other way but it would be awesome if you could move this one and change some of the others in this file too!

Schema: map[string]*schema.Schema{
"enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we should make this Required, that way people don't accidentally try to enable it by putting in the block but forgetting this field

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this as well. I think about how I work, and commenting out IAP enabled would "disable" it, without having to edit the line and set it as false. What's the standard here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something else to consider here, should we even have the enabled flag? Should it be implicit by providing the keys?

func expandIap(configured []interface{}) *compute.BackendServiceIAP {
data := configured[0].(map[string]interface{})
iap := &compute.BackendServiceIAP{
Enabled: data["enabled"].(bool),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without also setting ForceSendFields, these three fields won't appear in the request if they're set to empty (or false). It might still work since the outer block will still be there, but if you could check that you can disable iap using this code that would be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will double check and also add tests.

@@ -203,6 +229,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))
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Removed the enabled flag for IAP
IAP is now enabled when the client id and secret are set
IAP now correctly disables when IAP stanza is removed
Client secret is now correctly hashed against the secret hash stored on the server
@Cidan
Copy link
Contributor Author

Cidan commented Sep 28, 2017

Alright, had to make quite a few changes here. I'll try to explain my reasoning.

There seems to be a bug with the IAP API it self where if you disable IAP and attempt to zero out the secret (ie, remove the entire stanza), only the Enable -> false flag is set and the client secret is not cleared. As a result, the next time an apply is called, the state doesn't match and the apply will happen a second time, correctly clearing the key.

So, what I did was removed the notion of enable as an option. When IAP is added to a backend, you must provide a key and secret to enable it. Dropping the stanza for IAP disables it -- this is somewhat cleaner since the API it self requires the id and secret when enabling anyway. I've also made the service send up a default empty IAP object via ForceSendFields when the IAP stanza is missing, thus disabling the service if the previous state was enabled.

The IAP API never returns the secret once it's sent, only its' SHA256 sum, so I've added a function for comparing the SHA256 sum we get from the API vs the sum of the locally defined key.

Tests still to come.

@Cidan
Copy link
Contributor Author

Cidan commented Sep 29, 2017

Tests written and pass acceptance tests. @danawillow, how does it look now?

@Cidan Cidan changed the title initial work on adding IAP support for backend services Add IAP support for backend services Sep 29, 2017
Copy link
Contributor

@danawillow danawillow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this got a bit out-of-date, mind merging master in? Also it looks like the test failed:

$ make testacc TEST=./google TESTARGS='-run=TestAccComputeBackendService_withBackendAndIAP'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./google -v -run=TestAccComputeBackendService_withBackendAndIAP -timeout 120m
=== RUN   TestAccComputeBackendService_withBackendAndIAP
--- FAIL: TestAccComputeBackendService_withBackendAndIAP (92.66s)
	testing.go:435: Step 0 error: After applying this step, the plan was not empty:

		DIFF:

		UPDATE: google_compute_instance_group_manager.foobar
		  instance_template: "https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/instanceTemplates/tf-test-louv6hr7uj" => "<computed>"
		DESTROY/CREATE: google_compute_instance_template.foobar
		  can_ip_forward:                         "false" => "false"
		  disk.#:                                 "1" => "1"
		  disk.0.auto_delete:                     "true" => "true"
		  disk.0.boot:                            "true" => "true"
		  disk.0.device_name:                     "persistent-disk-0" => "" (forces new resource)
		  disk.0.disk_type:                       "pd-standard" => "<computed>"
		  disk.0.interface:                       "SCSI" => "<computed>"
		  disk.0.mode:                            "READ_WRITE" => "<computed>"
		  disk.0.source_image:                    "debian-8-jessie-v20160803" => "debian-8-jessie-v20160803"
		  disk.0.type:                            "PERSISTENT" => "<computed>"
		  machine_type:                           "n1-standard-1" => "n1-standard-1"
		  metadata_fingerprint:                   "zxKvLKF-n4o=" => "<computed>"
		  name:                                   "tf-test-louv6hr7uj" => "tf-test-louv6hr7uj"
		  network_interface.#:                    "1" => "1"
		  network_interface.0.network:            "default" => "default"
		  network_interface.0.subnetwork_project: "" => "<computed>"
		  project:                                "graphite-test-danahoffman-tf" => "<computed>"
		  scheduling.#:                           "1" => "<computed>"
		  self_link:                              "https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/instanceTemplates/tf-test-louv6hr7uj" => "<computed>"
		  tags_fingerprint:                       "" => "<computed>"

		STATE:

		google_compute_backend_service.lipsum:
		  ID = tf-test-0cr9fil17g
		  backend.# = 1
		  backend.2480256626.balancing_mode = UTILIZATION
		  backend.2480256626.capacity_scaler = 1
		  backend.2480256626.description =
		  backend.2480256626.group = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/zones/us-central1-f/instanceGroups/tf-test-j0hrqsyeol
		  backend.2480256626.max_rate = 0
		  backend.2480256626.max_rate_per_instance = 0
		  backend.2480256626.max_utilization = 0.8
		  connection_draining_timeout_sec = 300
		  description = Hello World 1234
		  enable_cdn = false
		  fingerprint = ogC68Pi2rHI=
		  health_checks.# = 1
		  health_checks.4210293762 = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/httpHealthChecks/tf-test-dx7gfqocw2
		  iap.# = 1
		  iap.0.oauth2_client_id = test
		  iap.0.oauth2_client_secret = test
		  name = tf-test-0cr9fil17g
		  port_name = http
		  protocol = HTTP
		  self_link = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/backendServices/tf-test-0cr9fil17g
		  session_affinity = NONE
		  timeout_sec = 10

		  Dependencies:
		    google_compute_http_health_check.default
		    google_compute_instance_group_manager.foobar
		google_compute_http_health_check.default:
		  ID = tf-test-dx7gfqocw2
		  check_interval_sec = 1
		  description =
		  healthy_threshold = 2
		  host =
		  name = tf-test-dx7gfqocw2
		  port = 80
		  project = graphite-test-danahoffman-tf
		  request_path = /
		  self_link = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/httpHealthChecks/tf-test-dx7gfqocw2
		  timeout_sec = 1
		  unhealthy_threshold = 2
		google_compute_instance_group_manager.foobar:
		  ID = tf-test-j0hrqsyeol
		  auto_healing_policies.# = 0
		  base_instance_name = foobar
		  description =
		  fingerprint = 8ibhjaSOFBE=
		  instance_group = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/zones/us-central1-f/instanceGroups/tf-test-j0hrqsyeol
		  instance_template = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/instanceTemplates/tf-test-louv6hr7uj
		  name = tf-test-j0hrqsyeol
		  named_port.# = 0
		  project = graphite-test-danahoffman-tf
		  self_link = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/zones/us-central1-f/instanceGroupManagers/tf-test-j0hrqsyeol
		  target_pools.# = 0
		  target_size = 1
		  update_strategy = RESTART
		  zone = us-central1-f

		  Dependencies:
		    google_compute_instance_template.foobar
		google_compute_instance_template.foobar:
		  ID = tf-test-louv6hr7uj
		  can_ip_forward = false
		  description =
		  disk.# = 1
		  disk.0.auto_delete = true
		  disk.0.boot = true
		  disk.0.device_name = persistent-disk-0
		  disk.0.disk_name =
		  disk.0.disk_size_gb = 0
		  disk.0.disk_type = pd-standard
		  disk.0.interface = SCSI
		  disk.0.mode = READ_WRITE
		  disk.0.source =
		  disk.0.source_image = debian-8-jessie-v20160803
		  disk.0.type = PERSISTENT
		  instance_description =
		  machine_type = n1-standard-1
		  metadata.% = 0
		  metadata_fingerprint = zxKvLKF-n4o=
		  name = tf-test-louv6hr7uj
		  network_interface.# = 1
		  network_interface.0.access_config.# = 0
		  network_interface.0.network = default
		  network_interface.0.network_ip =
		  network_interface.0.subnetwork =
		  network_interface.0.subnetwork_project =
		  project = graphite-test-danahoffman-tf
		  scheduling.# = 1
		  scheduling.0.automatic_restart = true
		  scheduling.0.on_host_maintenance = MIGRATE
		  scheduling.0.preemptible = false
		  self_link = https://www.googleapis.com/compute/v1/projects/graphite-test-danahoffman-tf/global/instanceTemplates/tf-test-louv6hr7uj
FAIL
exit status 1
FAIL	github.com/terraform-providers/terraform-provider-google/google	92.894s
make: *** [testacc] Error 1


func flattenIap(iap *compute.BackendServiceIAP) []map[string]interface{} {
if iap == nil {
return make([]map[string]interface{}, 1, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just curious, why 1, 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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?"
0, 1 will have the list not have anything in it, whereas 1, 1 will have the list have an empty map in it (see https://play.golang.org/p/SFCt6l5Fsw). The flatten functions for all other resources are currently using 0, 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, my apologies, I misread your question. Will adjust, :)

"oauth2_client_id": iap.Oauth2ClientId,
"oauth2_client_secret": iap.Oauth2ClientSecretSha256,
}
result = append(result, iapMap)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally up to you, but a shorter way to do this would be:

iapMap := map[string]interface{}{
  // properties
}
return []map[string]interface{}{iapMap}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't considered that -- I agree, much cleaner.

@@ -326,6 +379,13 @@ func expandBackendService(d *schema.ResourceData) compute.BackendService {
service := compute.BackendService{
Name: d.Get("name").(string),
HealthChecks: healthChecks,
Iap: &compute.BackendServiceIAP{
ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment as to why Iap has to be set (and why the three fields are force)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

},
})

if svc.TimeoutSec != 10 {
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@@ -273,6 +314,38 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi
}
}

func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.BackendService) resource.TestCheckFunc {
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. Will adjust.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

timeout_sec = %v

backend {
group = "${google_compute_instance_group_manager.foobar.instance_group}"
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@Cidan
Copy link
Contributor Author

Cidan commented Oct 21, 2017

@danawillow That's interesting on the test failure -- I can't reproduce it. Any guidance on your setup?

==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./google -v -run=TestAccComputeBackendService_withBackendAndIAP -timeout 120m
=== RUN   TestAccComputeBackendService_withBackendAndIAP
--- PASS: TestAccComputeBackendService_withBackendAndIAP (128.16s)
PASS
ok  	github.com/terraform-providers/terraform-provider-google/google	128.190s


func flattenIap(iap *compute.BackendServiceIAP) []map[string]interface{} {
if iap == nil {
return make([]map[string]interface{}, 1, 1)
Copy link
Contributor

Choose a reason for hiding this comment

The 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?"
0, 1 will have the list not have anything in it, whereas 1, 1 will have the list have an empty map in it (see https://play.golang.org/p/SFCt6l5Fsw). The flatten functions for all other resources are currently using 0, 1.

Config: testAccComputeBackendService_withBackend(
serviceName, igName, itName, checkName, 10),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeBackendServiceExists(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checks that the backend service exists, but it doesn't check that IAP actually got disabled (I'd highly recommend just adding that check in below with the other checks on svc)

@@ -273,6 +314,38 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi
}
}

func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.BackendService) resource.TestCheckFunc {
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

@Cidan
Copy link
Contributor Author

Cidan commented Oct 31, 2017

@danawillow Alright, let's try this one more time!

Made the recommended changes, and testing now checks for IAP enabled and disabled properly.

@danawillow
Copy link
Contributor

Thanks @Cidan!

@danawillow danawillow merged commit d37f352 into hashicorp:master Oct 31, 2017
@rosbo rosbo mentioned this pull request Dec 11, 2017
@Cidan Cidan deleted the add_iap branch December 11, 2017 17:47
luis-silva pushed a commit to luis-silva/terraform-provider-google that referenced this pull request May 21, 2019
@ghost
Copy link

ghost commented Mar 30, 2020

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks!

@ghost ghost locked and limited conversation to collaborators Mar 30, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants