From a1121de2bd49d4a15e6a12418b5414390c3a5630 Mon Sep 17 00:00:00 2001 From: The Magician Date: Wed, 18 Sep 2024 12:48:33 -0700 Subject: [PATCH] Add Private Service Connect to Looker (#11487) (#8211) [upstream:d65f0b7a1bf7a8d4c6aa02d9540542d3d8695429] Signed-off-by: Modular Magician --- .changelog/11487.txt | 3 + .../looker/resource_looker_instance.go | 245 ++++++++++++++++++ ...resource_looker_instance_generated_test.go | 47 ++++ website/docs/r/looker_instance.html.markdown | 66 +++++ 4 files changed, 361 insertions(+) create mode 100644 .changelog/11487.txt diff --git a/.changelog/11487.txt b/.changelog/11487.txt new file mode 100644 index 0000000000..58432fee0c --- /dev/null +++ b/.changelog/11487.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +looker: added `psc_enabled` and `psc_config` to `google_looker_instance` resource +``` \ No newline at end of file diff --git a/google-beta/services/looker/resource_looker_instance.go b/google-beta/services/looker/resource_looker_instance.go index f9c0758349..a68bc17da5 100644 --- a/google-beta/services/looker/resource_looker_instance.go +++ b/google-beta/services/looker/resource_looker_instance.go @@ -354,6 +354,58 @@ disrupt service.`, Description: `Whether private IP is enabled on the Looker instance.`, Default: false, }, + "psc_config": { + Type: schema.TypeList, + Optional: true, + Description: `Information for Private Service Connect (PSC) setup for a Looker instance.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_vpcs": { + Type: schema.TypeList, + Optional: true, + Description: `List of VPCs that are allowed ingress into the Looker instance.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "service_attachments": { + Type: schema.TypeList, + Optional: true, + Description: `List of egress service attachment configurations.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "local_fqdn": { + Type: schema.TypeString, + Optional: true, + Description: `Fully qualified domain name that will be used in the private DNS record created for the service attachment.`, + }, + "target_service_attachment_uri": { + Type: schema.TypeString, + Optional: true, + Description: `URI of the service attachment to connect to.`, + }, + "connection_status": { + Type: schema.TypeString, + Computed: true, + Description: `Status of the service attachment connection.`, + }, + }, + }, + }, + "looker_service_attachment_uri": { + Type: schema.TypeString, + Computed: true, + Description: `URI of the Looker service attachment.`, + }, + }, + }, + }, + "psc_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: `Whether Public Service Connect (PSC) is enabled on the Looker instance`, + }, "public_ip_enabled": { Type: schema.TypeBool, Optional: true, @@ -517,6 +569,18 @@ func resourceLookerInstanceCreate(d *schema.ResourceData, meta interface{}) erro } else if v, ok := d.GetOkExists("private_ip_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(privateIpEnabledProp)) && (ok || !reflect.DeepEqual(v, privateIpEnabledProp)) { obj["privateIpEnabled"] = privateIpEnabledProp } + pscConfigProp, err := expandLookerInstancePscConfig(d.Get("psc_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("psc_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(pscConfigProp)) && (ok || !reflect.DeepEqual(v, pscConfigProp)) { + obj["pscConfig"] = pscConfigProp + } + pscEnabledProp, err := expandLookerInstancePscEnabled(d.Get("psc_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("psc_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(pscEnabledProp)) && (ok || !reflect.DeepEqual(v, pscEnabledProp)) { + obj["pscEnabled"] = pscEnabledProp + } publicIpEnabledProp, err := expandLookerInstancePublicIpEnabled(d.Get("public_ip_enabled"), d, config) if err != nil { return err @@ -694,6 +758,12 @@ func resourceLookerInstanceRead(d *schema.ResourceData, meta interface{}) error if err := d.Set("private_ip_enabled", flattenLookerInstancePrivateIpEnabled(res["privateIpEnabled"], d, config)); err != nil { return fmt.Errorf("Error reading Instance: %s", err) } + if err := d.Set("psc_config", flattenLookerInstancePscConfig(res["pscConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading Instance: %s", err) + } + if err := d.Set("psc_enabled", flattenLookerInstancePscEnabled(res["pscEnabled"], d, config)); err != nil { + return fmt.Errorf("Error reading Instance: %s", err) + } if err := d.Set("public_ip_enabled", flattenLookerInstancePublicIpEnabled(res["publicIpEnabled"], d, config)); err != nil { return fmt.Errorf("Error reading Instance: %s", err) } @@ -777,6 +847,18 @@ func resourceLookerInstanceUpdate(d *schema.ResourceData, meta interface{}) erro } else if v, ok := d.GetOkExists("private_ip_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, privateIpEnabledProp)) { obj["privateIpEnabled"] = privateIpEnabledProp } + pscConfigProp, err := expandLookerInstancePscConfig(d.Get("psc_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("psc_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, pscConfigProp)) { + obj["pscConfig"] = pscConfigProp + } + pscEnabledProp, err := expandLookerInstancePscEnabled(d.Get("psc_enabled"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("psc_enabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, pscEnabledProp)) { + obj["pscEnabled"] = pscEnabledProp + } publicIpEnabledProp, err := expandLookerInstancePublicIpEnabled(d.Get("public_ip_enabled"), d, config) if err != nil { return err @@ -843,6 +925,15 @@ func resourceLookerInstanceUpdate(d *schema.ResourceData, meta interface{}) erro updateMask = append(updateMask, "privateIpEnabled") } + if d.HasChange("psc_config") { + updateMask = append(updateMask, "psc_config.allowed_vpcs", + "psc_config.service_attachments") + } + + if d.HasChange("psc_enabled") { + updateMask = append(updateMask, "pscEnabled") + } + if d.HasChange("public_ip_enabled") { updateMask = append(updateMask, "publicIpEnabled") } @@ -1412,6 +1503,67 @@ func flattenLookerInstancePrivateIpEnabled(v interface{}, d *schema.ResourceData return v } +func flattenLookerInstancePscConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["allowed_vpcs"] = + flattenLookerInstancePscConfigAllowedVpcs(original["allowedVpcs"], d, config) + transformed["looker_service_attachment_uri"] = + flattenLookerInstancePscConfigLookerServiceAttachmentUri(original["lookerServiceAttachmentUri"], d, config) + transformed["service_attachments"] = + flattenLookerInstancePscConfigServiceAttachments(original["serviceAttachments"], d, config) + return []interface{}{transformed} +} +func flattenLookerInstancePscConfigAllowedVpcs(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenLookerInstancePscConfigLookerServiceAttachmentUri(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenLookerInstancePscConfigServiceAttachments(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "connection_status": flattenLookerInstancePscConfigServiceAttachmentsConnectionStatus(original["connectionStatus"], d, config), + "local_fqdn": flattenLookerInstancePscConfigServiceAttachmentsLocalFqdn(original["localFqdn"], d, config), + "target_service_attachment_uri": flattenLookerInstancePscConfigServiceAttachmentsTargetServiceAttachmentUri(original["targetServiceAttachmentUri"], d, config), + }) + } + return transformed +} +func flattenLookerInstancePscConfigServiceAttachmentsConnectionStatus(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenLookerInstancePscConfigServiceAttachmentsLocalFqdn(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenLookerInstancePscConfigServiceAttachmentsTargetServiceAttachmentUri(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenLookerInstancePscEnabled(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenLookerInstancePublicIpEnabled(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { return v } @@ -1898,6 +2050,99 @@ func expandLookerInstancePrivateIpEnabled(v interface{}, d tpgresource.Terraform return v, nil } +func expandLookerInstancePscConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedAllowedVpcs, err := expandLookerInstancePscConfigAllowedVpcs(original["allowed_vpcs"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedAllowedVpcs); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["allowedVpcs"] = transformedAllowedVpcs + } + + transformedLookerServiceAttachmentUri, err := expandLookerInstancePscConfigLookerServiceAttachmentUri(original["looker_service_attachment_uri"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLookerServiceAttachmentUri); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["lookerServiceAttachmentUri"] = transformedLookerServiceAttachmentUri + } + + transformedServiceAttachments, err := expandLookerInstancePscConfigServiceAttachments(original["service_attachments"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedServiceAttachments); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["serviceAttachments"] = transformedServiceAttachments + } + + return transformed, nil +} + +func expandLookerInstancePscConfigAllowedVpcs(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLookerInstancePscConfigLookerServiceAttachmentUri(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLookerInstancePscConfigServiceAttachments(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedConnectionStatus, err := expandLookerInstancePscConfigServiceAttachmentsConnectionStatus(original["connection_status"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedConnectionStatus); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["connectionStatus"] = transformedConnectionStatus + } + + transformedLocalFqdn, err := expandLookerInstancePscConfigServiceAttachmentsLocalFqdn(original["local_fqdn"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocalFqdn); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["localFqdn"] = transformedLocalFqdn + } + + transformedTargetServiceAttachmentUri, err := expandLookerInstancePscConfigServiceAttachmentsTargetServiceAttachmentUri(original["target_service_attachment_uri"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTargetServiceAttachmentUri); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["targetServiceAttachmentUri"] = transformedTargetServiceAttachmentUri + } + + req = append(req, transformed) + } + return req, nil +} + +func expandLookerInstancePscConfigServiceAttachmentsConnectionStatus(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLookerInstancePscConfigServiceAttachmentsLocalFqdn(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLookerInstancePscConfigServiceAttachmentsTargetServiceAttachmentUri(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLookerInstancePscEnabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + func expandLookerInstancePublicIpEnabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { return v, nil } diff --git a/google-beta/services/looker/resource_looker_instance_generated_test.go b/google-beta/services/looker/resource_looker_instance_generated_test.go index bf733b6f9b..ddde111a9e 100644 --- a/google-beta/services/looker/resource_looker_instance_generated_test.go +++ b/google-beta/services/looker/resource_looker_instance_generated_test.go @@ -321,6 +321,53 @@ resource "google_looker_instance" "looker-instance" { `, context) } +func TestAccLookerInstance_lookerInstancePscExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckLookerInstanceDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccLookerInstance_lookerInstancePscExample(context), + }, + { + ResourceName: "google_looker_instance.looker-instance", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "oauth_config", "region"}, + }, + }, + }) +} + +func testAccLookerInstance_lookerInstancePscExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_looker_instance" "looker-instance" { + name = "tf-test-my-instance%{random_suffix}" + platform_edition = "LOOKER_CORE_ENTERPRISE_ANNUAL" + region = "us-central1" + private_ip_enabled = false + public_ip_enabled = false + psc_enabled = true + oauth_config { + client_id = "tf-test-my-client-id%{random_suffix}" + client_secret = "tf-test-my-client-secret%{random_suffix}" + } + psc_config { + allowed_vpcs = ["projects/test-project/global/networks/test"] + # update only + # service_attachments = [{local_fqdn: "www.local-fqdn.com" target_service_attachment_uri: "projects/my-project/regions/us-east1/serviceAttachments/sa"}] + } +} +`, context) +} + func testAccCheckLookerInstanceDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/website/docs/r/looker_instance.html.markdown b/website/docs/r/looker_instance.html.markdown index e9fd2db320..7d8c43acd8 100644 --- a/website/docs/r/looker_instance.html.markdown +++ b/website/docs/r/looker_instance.html.markdown @@ -223,6 +223,33 @@ resource "google_looker_instance" "looker-instance" { } } ``` + +## Example Usage - Looker Instance Psc + + +```hcl +resource "google_looker_instance" "looker-instance" { + name = "my-instance" + platform_edition = "LOOKER_CORE_ENTERPRISE_ANNUAL" + region = "us-central1" + private_ip_enabled = false + public_ip_enabled = false + psc_enabled = true + oauth_config { + client_id = "my-client-id" + client_secret = "my-client-secret" + } + psc_config { + allowed_vpcs = ["projects/test-project/global/networks/test"] + # update only + # service_attachments = [{local_fqdn: "www.local-fqdn.com" target_service_attachment_uri: "projects/my-project/regions/us-east1/serviceAttachments/sa"}] + } +} +``` ## Argument Reference @@ -292,6 +319,15 @@ The following arguments are supported: (Optional) Whether private IP is enabled on the Looker instance. +* `psc_config` - + (Optional) + Information for Private Service Connect (PSC) setup for a Looker instance. + Structure is [documented below](#nested_psc_config). + +* `psc_enabled` - + (Optional) + Whether Public Service Connect (PSC) is enabled on the Looker instance + * `public_ip_enabled` - (Optional) Whether public IP is enabled on the Looker instance. @@ -467,6 +503,36 @@ The following arguments are supported: (Required) The client secret for the Oauth config. +The `psc_config` block supports: + +* `allowed_vpcs` - + (Optional) + List of VPCs that are allowed ingress into the Looker instance. + +* `looker_service_attachment_uri` - + (Output) + URI of the Looker service attachment. + +* `service_attachments` - + (Optional) + List of egress service attachment configurations. + Structure is [documented below](#nested_service_attachments). + + +The `service_attachments` block supports: + +* `connection_status` - + (Output) + Status of the service attachment connection. + +* `local_fqdn` - + (Optional) + Fully qualified domain name that will be used in the private DNS record created for the service attachment. + +* `target_service_attachment_uri` - + (Optional) + URI of the service attachment to connect to. + The `user_metadata` block supports: * `additional_viewer_user_count` -