Skip to content

Commit

Permalink
New looker core module (GoogleCloudPlatform#2565)
Browse files Browse the repository at this point in the history
* new looker core module
---------
Co-authored-by: Wiktor Niesiobędzki <[email protected]>
  • Loading branch information
simonebruzzechesse authored Sep 20, 2024
1 parent 4bacbf5 commit 1f2bdd0
Show file tree
Hide file tree
Showing 9 changed files with 956 additions and 0 deletions.
195 changes: 195 additions & 0 deletions modules/looker-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Looker Core module

This module manages the creation of a [Looker Core instance](https://cloud.google.com/looker/docs/looker-core).

This module accepts Oauth client ID and secret in the input variable `oauth_config` in case you have
already [set up an oauth client and credentials](https://cloud.google.com/looker/docs/looker-core-create-oauth).
If that is not the case it is possible to specify support_email in the same variable `oauth_config` for a default oauth
client id and secret setup within the terraform script, be aware that **such an oauth client id is not suitable for
authenticating end users**, and it is only used to provision the looker core instance.
You'll still be forced to create a new oauth and update the looker core instance from the console (or gcloud) as there
is no terraform support for these resources.


> [!WARNING]
> Please be aware that, at the time of this writing, deleting the looker core instance via terraform is not possible due
> to https://github.com/hashicorp/terraform-provider-google/issues/19467. The work-around is to delete the instance from the
> console (or gcloud with force option) and remove the corresponding resource from the terraform state.
<!-- TOC -->

* [Looker Core module](#looker-core-module)
* [Examples](#examples)
* [Simple example](#simple-example)
* [Looker Core private instance with PSA](#looker-core-private-instance-with-psa)
* [Looker Core full example](#looker-core-full-example)
* [Variables](#variables)
* [Outputs](#outputs)

<!-- TOC -->

## Examples

### Simple example

This example shows how to set up a public Looker Core instance.

```hcl
module "looker" {
source = "./fabric/modules/looker-core"
project_id = var.project_id
region = var.region
name = "looker"
network_config = {
public = true
}
oauth_config = {
support_email = "[email protected]"
}
}
# tftest modules=1 resources=3 inventory=simple.yaml
```

### Looker Core private instance with PSA

```hcl
module "project" {
source = "./fabric/modules/project"
billing_account = var.billing_account_id
parent = var.folder_id
name = "looker"
prefix = var.prefix
services = [
"servicenetworking.googleapis.com",
"looker.googleapis.com",
]
}
module "vpc" {
source = "./fabric/modules/net-vpc"
project_id = module.project.project_id
name = "my-network"
psa_configs = [
{
ranges = { looker = "10.60.0.0/16" }
}
]
}
module "looker" {
source = "./fabric/modules/looker-core"
project_id = module.project.project_id
region = var.region
name = "looker"
network_config = {
psa_config = {
network = module.vpc.id
}
}
oauth_config = {
support_email = "[email protected]"
}
platform_edition = "LOOKER_CORE_ENTERPRISE_ANNUAL"
}
# tftest modules=3 resources=16 inventory=psa.yaml
```

### Looker Core full example

```hcl
module "project" {
source = "./fabric/modules/project"
billing_account = var.billing_account_id
parent = var.folder_id
name = "looker"
prefix = var.prefix
services = [
"cloudkms.googleapis.com",
"iap.googleapis.com",
"looker.googleapis.com",
"servicenetworking.googleapis.com"
]
}
module "vpc" {
source = "./fabric/modules/net-vpc"
project_id = module.project.project_id
name = "my-network"
psa_configs = [
{
ranges = { looker = "10.60.0.0/16" }
}
]
}
module "kms" {
source = "./fabric/modules/kms"
project_id = module.project.project_id
keyring = {
location = var.region
name = "keyring"
}
keys = {
"key-regional" = {
}
}
iam = {
"roles/cloudkms.cryptoKeyEncrypterDecrypter" = [
module.project.service_agents.looker.iam_email
]
}
}
module "looker" {
source = "./fabric/modules/looker-core"
project_id = module.project.project_id
region = var.region
name = "looker"
admin_settings = {
allowed_email_domains = ["google.com"]
}
encryption_config = {
kms_key_name = module.kms.keys.key-regional.id
}
network_config = {
psa_config = {
network = module.vpc.id
}
}
oauth_config = {
client_id = "xxxxxxxxx"
client_secret = "xxxxxxxx"
}
platform_edition = "LOOKER_CORE_ENTERPRISE_ANNUAL"
}
# tftest modules=4 resources=22 inventory=full.yaml
```
<!-- BEGIN TFDOC -->
## Variables

| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [name](variables.tf#L85) | Name of the looker core instance. | <code>string</code> || |
| [network_config](variables.tf#L90) | Network configuration for cluster and instance. Only one between psa_config and psc_config can be used. | <code title="object&#40;&#123;&#10; psa_config &#61; optional&#40;object&#40;&#123;&#10; network &#61; string&#10; allocated_ip_range &#61; optional&#40;string&#41;&#10; enable_public_ip &#61; optional&#40;bool, false&#41;&#10; enable_private_ip &#61; optional&#40;bool, true&#41;&#10; &#125;&#41;&#41;&#10; public &#61; optional&#40;bool, false&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> || |
| [oauth_config](variables.tf#L108) | Looker Core Oauth config. Either client ID and secret (existing oauth client) or support email (temporary internal oauth client setup) must be specified. | <code title="object&#40;&#123;&#10; client_id &#61; optional&#40;string, null&#41;&#10; client_secret &#61; optional&#40;string, null&#41;&#10; support_email &#61; optional&#40;string, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> || |
| [project_id](variables.tf#L141) | The ID of the project where this instances will be created. | <code>string</code> || |
| [region](variables.tf#L146) | Region for the Looker core instance. | <code>string</code> || |
| [admin_settings](variables.tf#L17) | Looker Core admins settings. | <code title="object&#40;&#123;&#10; allowed_email_domains &#61; list&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [encryption_config](variables.tf#L26) | Set encryption configuration. KMS name format: 'projects/[PROJECT]/locations/[REGION]/keyRings/[RING]/cryptoKeys/[KEY_NAME]'. | <code title="object&#40;&#123;&#10; kms_key_name &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [maintenance_config](variables.tf#L35) | Set maintenance window configuration and maintenance deny period (up to 90 days). Date format: 'yyyy-mm-dd'. | <code title="object&#40;&#123;&#10; maintenance_window &#61; optional&#40;object&#40;&#123;&#10; day &#61; optional&#40;string, &#34;SUNDAY&#34;&#41;&#10; start_time &#61; optional&#40;object&#40;&#123;&#10; hours &#61; optional&#40;number, 23&#41;&#10; minutes &#61; optional&#40;number, 0&#41;&#10; seconds &#61; optional&#40;number, 0&#41;&#10; nanos &#61; optional&#40;number, 0&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, null&#41;&#10; deny_maintenance_period &#61; optional&#40;object&#40;&#123;&#10; start_date &#61; object&#40;&#123;&#10; year &#61; number&#10; month &#61; number&#10; day &#61; number&#10; &#125;&#41;&#10; end_date &#61; object&#40;&#123;&#10; year &#61; number&#10; month &#61; number&#10; day &#61; number&#10; &#125;&#41;&#10; start_time &#61; optional&#40;object&#40;&#123;&#10; hours &#61; optional&#40;number, 23&#41;&#10; minutes &#61; optional&#40;number, 0&#41;&#10; seconds &#61; optional&#40;number, 0&#41;&#10; nanos &#61; optional&#40;number, 0&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10; &#125;&#41;, null&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>&#123;&#125;</code> |
| [platform_edition](variables.tf#L121) | Platform editions for a Looker instance. Each edition maps to a set of instance features, like its size. | <code>string</code> | | <code>&#34;LOOKER_CORE_TRIAL&#34;</code> |
| [prefix](variables.tf#L131) | Optional prefix used to generate instance names. | <code>string</code> | | <code>null</code> |

## Outputs

| name | description | sensitive |
|---|---|:---:|
| [egress_public_ip](outputs.tf#L17) | Public IP address of Looker instance for egress. | |
| [id](outputs.tf#L22) | Fully qualified primary instance id. | |
| [ingress_private_ip](outputs.tf#L27) | Private IP address of Looker instance for ingress. | |
| [ingress_public_ip](outputs.tf#L32) | Public IP address of Looker instance for ingress. | |
| [instance](outputs.tf#L37) | Looker Core instance resource. ||
| [instance_name](outputs.tf#L43) | Name of the looker instance. | |
| [looker_uri](outputs.tf#L48) | Looker core URI. | |
| [looker_version](outputs.tf#L53) | Looker core version. | |
<!-- END TFDOC -->
108 changes: 108 additions & 0 deletions modules/looker-core/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

locals {
bootstrap_oauth_client = var.oauth_config.client_secret == null || var.oauth_config.client_id == null
looker_instance_name = "${local.prefix}${var.name}"
oauth_client_id = local.bootstrap_oauth_client ? google_iap_client.looker_client[0].client_id : var.oauth_config.client_id
oauth_client_secret = local.bootstrap_oauth_client ? google_iap_client.looker_client[0].secret : var.oauth_config.client_secret
prefix = var.prefix == null ? "" : "${var.prefix}-"
}

resource "google_looker_instance" "looker" {
project = var.project_id
name = local.looker_instance_name
consumer_network = try(var.network_config.psa_config.network, null)
platform_edition = var.platform_edition
private_ip_enabled = try(var.network_config.psa_config.enable_private_ip, null)
public_ip_enabled = coalesce(var.network_config.public, false) || try(var.network_config.psa_config.enable_public_ip, false)
region = var.region
reserved_range = try(var.network_config.psa_config.allocated_ip_range, null)

oauth_config {
client_id = local.oauth_client_id
client_secret = local.oauth_client_secret
}

dynamic "admin_settings" {
for_each = var.admin_settings != null ? [""] : []
content {
allowed_email_domains = var.admin_settings.allowed_email_domains
}
}
dynamic "deny_maintenance_period" {
for_each = var.maintenance_config.deny_maintenance_period != null ? [1] : []
content {
start_date {
year = var.maintenance_config.deny_maintenance_period.start_date.year
month = var.maintenance_config.deny_maintenance_period.start_date.month
day = var.maintenance_config.deny_maintenance_period.start_date.day
}
end_date {
year = var.maintenance_config.deny_maintenance_period.start_date.year
month = var.maintenance_config.deny_maintenance_period.start_date.month
day = var.maintenance_config.deny_maintenance_period.start_date.day
}
time {
hours = var.maintenance_config.deny_maintenance_period.start_times.hours
minutes = var.maintenance_config.deny_maintenance_period.start_times.minutes
seconds = var.maintenance_config.deny_maintenance_period.start_times.seconds
nanos = var.maintenance_config.deny_maintenance_period.start_times.nanos
}
}
}
dynamic "encryption_config" {
for_each = var.encryption_config != null ? [""] : []
content {
kms_key_name = var.encryption_config.kms_key_name
}
}
dynamic "maintenance_window" {
for_each = var.maintenance_config.maintenance_window != null ? [""] : []
content {
day_of_week = var.maintenance_config.maintenance_window.day
start_time {
hours = var.maintenance_config.maintenance_window.start_times.hours
minutes = var.maintenance_config.maintenance_window.start_times.minutes
seconds = var.maintenance_config.maintenance_window.start_times.seconds
nanos = var.maintenance_config.maintenance_window.start_times.nanos
}
}
}
lifecycle {
ignore_changes = [
oauth_config # do not replace target oauth client updated on the console with default one
]
}
}


# Only "Organization Internal" brands can be created programmatically via API. To convert it into an external brands please use the GCP Console.
resource "google_iap_brand" "looker_brand" {
count = local.bootstrap_oauth_client ? 1 : 0
support_email = var.oauth_config.support_email
# application_title = "Looker Core Application"
application_title = "Cloud IAP protected Application"
project = var.project_id
}

# Only internal org clients can be created via declarative tools. External clients must be manually created via the GCP console.
# This is a temporary IAP oauth client to be replaced after Looker Core is provisioned.
resource "google_iap_client" "looker_client" {
count = local.bootstrap_oauth_client ? 1 : 0
display_name = "Looker Core default oauth client."
brand = google_iap_brand.looker_brand[0].name
}
56 changes: 56 additions & 0 deletions modules/looker-core/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

output "egress_public_ip" {
description = "Public IP address of Looker instance for egress."
value = google_looker_instance.looker.egress_public_ip
}

output "id" {
description = "Fully qualified primary instance id."
value = google_looker_instance.looker.id
}

output "ingress_private_ip" {
description = "Private IP address of Looker instance for ingress."
value = google_looker_instance.looker.ingress_private_ip
}

output "ingress_public_ip" {
description = "Public IP address of Looker instance for ingress."
value = google_looker_instance.looker.ingress_public_ip
}

output "instance" {
description = "Looker Core instance resource."
value = google_looker_instance.looker.id
sensitive = true
}

output "instance_name" {
description = "Name of the looker instance."
value = google_looker_instance.looker.name
}

output "looker_uri" {
description = "Looker core URI."
value = google_looker_instance.looker.looker_uri
}

output "looker_version" {
description = "Looker core version."
value = google_looker_instance.looker.looker_version
}
Loading

0 comments on commit 1f2bdd0

Please sign in to comment.