diff --git a/modules/__experimental/cloud-function-scheduled/README.md b/modules/__experimental/cloud-function-scheduled/README.md new file mode 100644 index 0000000000..beeb88cf70 --- /dev/null +++ b/modules/__experimental/cloud-function-scheduled/README.md @@ -0,0 +1,42 @@ +# Scheduled Google Cloud Function Module + +This module manages a background Cloud Function scheduled via a recurring Cloud Scheduler job. It also manages the required dependencies: a service account for the cloud function with optional IAM bindings, the PubSub topic used for the function trigger, and optionally the GCS bucket used for the code bundle. + +## Example + +```hcl +module "function" { + source = "./modules/cloud-function-scheduled" + project_id = "myproject" + name = "myfunction" + bundle_config = { + source_dir = "../cf" + output_path = "../bundle.zip" + } +} +``` + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| bundle_config | Cloud function code bundle configuration, output path is a zip file. | object({...}) | ✓ | | +| name | Name used for resources (schedule, topic, etc.). | string | ✓ | | +| project_id | Project id used for all resources. | string | ✓ | | +| *bucket_name* | Name of the bucket that will be used for the function code, leave null to create one. | string | | null | +| *function_config* | Cloud function configuration. | object({...}) | | ... | +| *prefixes* | Optional prefixes for resource ids, null prefixes will be ignored. | object({...}) | | null | +| *region* | Region used for all resources. | string | | us-central1 | +| *schedule_config* | Cloud function scheduler job configuration, leave data null to pass the name variable. | object({...}) | | ... | +| *service_account_iam_roles* | IAM roles assigned to the service account at the project level. | list(string) | | [] | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| bucket_name | Bucket name. | | +| function_name | Cloud function name. | | +| service_account_email | Service account email. | | +| topic_id | PubSub topic id. | | + diff --git a/modules/__experimental/cloud-function-scheduled/main.tf b/modules/__experimental/cloud-function-scheduled/main.tf new file mode 100644 index 0000000000..ca197f98b1 --- /dev/null +++ b/modules/__experimental/cloud-function-scheduled/main.tf @@ -0,0 +1,132 @@ +/** + * Copyright 2020 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 { + bucket = ( + var.bucket_name != null + ? var.bucket_name + : google_storage_bucket.bucket[0].name + ) + job_data = ( + var.schedule_config.pubsub_data == null || var.schedule_config.pubsub_data == "" + ? var.name + : var.schedule_config.pubsub_data + ) + prefixes = ( + var.prefixes == null + ? {} + : { + for k, v in var.prefixes : + k => v != null && v != "" ? "${v}-${var.name}" : var.name + } + ) + service_account = "serviceAccount:${google_service_account.service_account.email}" +} + +############################################################################### +# Scheduler / PubSub # +############################################################################### + +resource "google_pubsub_topic" "topic" { + project = var.project_id + name = lookup(local.prefixes, "topic", var.name) +} + +resource "google_cloud_scheduler_job" "job" { + project = var.project_id + region = var.region + name = lookup(local.prefixes, "job", var.name) + schedule = var.schedule_config.schedule + time_zone = var.schedule_config.time_zone + + pubsub_target { + attributes = {} + topic_name = google_pubsub_topic.topic.id + data = base64encode(local.job_data) + } +} + +############################################################################### +# Cloud Function service account and IAM # +############################################################################### + +resource "google_service_account" "service_account" { + project = var.project_id + account_id = lookup(local.prefixes, "service_account", var.name) + display_name = "Terraform-managed" +} + +resource "google_project_iam_member" "service_account" { + for_each = toset(var.service_account_iam_roles) + project = var.project_id + role = each.value + member = local.service_account +} + +############################################################################### +# Cloud Function and GCS code bundle # +############################################################################### + +resource "google_cloudfunctions_function" "function" { + project = var.project_id + region = var.region + name = lookup(local.prefixes, "function", var.name) + description = "Terraform managed." + runtime = var.function_config.runtime + available_memory_mb = var.function_config.memory + max_instances = var.function_config.instances + timeout = var.function_config.timeout + entry_point = var.function_config.entry_point + service_account_email = google_service_account.service_account.email + + # source_repository { + # url = var.source_repository_url + # } + + event_trigger { + event_type = "providers/cloud.pubsub/eventTypes/topic.publish" + resource = google_pubsub_topic.topic.id + } + + source_archive_bucket = local.bucket + source_archive_object = google_storage_bucket_object.bundle.name +} + +resource "google_storage_bucket" "bucket" { + count = var.bucket_name == null ? 1 : 0 + project = var.project_id + name = lookup(local.prefixes, "bucket", var.name) + lifecycle_rule { + action { + type = "Delete" + } + condition { + age = "30" + } + } +} + +resource "google_storage_bucket_object" "bundle" { + name = "bundle-${data.archive_file.bundle.output_md5}.zip" + bucket = local.bucket + source = data.archive_file.bundle.output_path +} + +data "archive_file" "bundle" { + type = "zip" + source_dir = var.bundle_config.source_dir + output_path = var.bundle_config.output_path +} diff --git a/modules/__experimental/cloud-function-scheduled/outputs.tf b/modules/__experimental/cloud-function-scheduled/outputs.tf new file mode 100644 index 0000000000..2d47bd09c4 --- /dev/null +++ b/modules/__experimental/cloud-function-scheduled/outputs.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2020 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 "bucket_name" { + description = "Bucket name." + value = local.bucket +} + +output "function_name" { + description = "Cloud function name." + value = google_cloudfunctions_function.function.name +} + +output "service_account_email" { + description = "Service account email." + value = google_service_account.service_account.email +} + +output "topic_id" { + description = "PubSub topic id." + value = google_pubsub_topic.topic.id +} diff --git a/modules/__experimental/cloud-function-scheduled/variables.tf b/modules/__experimental/cloud-function-scheduled/variables.tf new file mode 100644 index 0000000000..6a33f37f2c --- /dev/null +++ b/modules/__experimental/cloud-function-scheduled/variables.tf @@ -0,0 +1,100 @@ +/** + * Copyright 2020 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. + */ + +variable "bucket_name" { + description = "Name of the bucket that will be used for the function code, leave null to create one." + type = string + default = null +} + +variable "bundle_config" { + description = "Cloud function code bundle configuration, output path is a zip file." + type = object({ + source_dir = string + output_path = string + }) +} + +variable "function_config" { + description = "Cloud function configuration." + type = object({ + entry_point = string + instances = number + memory = number + runtime = string + timeout = number + }) + default = { + entry_point = "main" + instances = 1 + memory = 256 + runtime = "python37" + timeout = 180 + } +} + +variable "name" { + description = "Name used for resources (schedule, topic, etc.)." + type = string +} + +variable "prefixes" { + description = "Optional prefixes for resource ids, null prefixes will be ignored." + type = object({ + bucket = string + function = string + job = string + service_account = string + topic = string + }) + default = null +} + +variable "project_id" { + description = "Project id used for all resources." + type = string +} + +variable "region" { + description = "Region used for all resources." + type = string + default = "us-central1" +} + +variable "schedule_config" { + description = "Cloud function scheduler job configuration, leave data null to pass the name variable." + type = object({ + pubsub_data = string + schedule = string + time_zone = string + }) + default = { + schedule = "*/10 * * * *" + pubsub_data = null + time_zone = "UTC" + } +} + +variable "service_account_iam_roles" { + description = "IAM roles assigned to the service account at the project level." + type = list(string) + default = [] +} + +# variable "source_repository_url" { +# type = string +# default = "" +# } diff --git a/modules/__sandbox/playground/outputs.tf b/modules/__experimental/cloud-function-scheduled/versions.tf similarity index 72% rename from modules/__sandbox/playground/outputs.tf rename to modules/__experimental/cloud-function-scheduled/versions.tf index 2a0d557aa2..bc4c2a9d71 100644 --- a/modules/__sandbox/playground/outputs.tf +++ b/modules/__experimental/cloud-function-scheduled/versions.tf @@ -1,5 +1,5 @@ /** - * Copyright 2019 Google LLC + * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,6 @@ * limitations under the License. */ -output "folder" { - description = "Folder resource." - value = google_folder.folder -} - -output "folder_name" { - description = "Folder name." - value = google_folder.folder.name +terraform { + required_version = ">= 0.12.6" } diff --git a/modules/__sandbox/playground/README.md b/modules/__sandbox/playground/README.md deleted file mode 100644 index 0bafc37d5e..0000000000 --- a/modules/__sandbox/playground/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# Playground folder module - -Simple module to create a playground folder, and grant all relevant permissions on the correct resources (folder, billing account, organization) to enable specific identities to have complete control under it. - -Special considerations - -- setting Shared VPC Admin roles at the folder level is buggy and not guaranteed to work, ideally those should be set at the organization level -- if administrators should manage any project under the folder regardless of the identity that created them, the extra role `roles/owner` has to be added to the `folder_roles` variable; testing different levels of access will then require extra identities -- users from outside the org need the extra role `roles/browser` in the `organization_roles` variable - -To retrofit the module after creation, just import an existing folder and apply. - -## Example - -```hcl -module "playground-demo" { - source = "./playground" - administrators = ["user:user1@example.com", "group:group1@example.com"] - billing_account = "0123ABC-0123ABC-0123ABC" - name = "Playground test" - organization_id = 1234567890 - parent = "folders/1234567890" -} -``` - - -## Variables - -| name | description | type | required | default | -|---|---|:---: |:---:|:---:| -| billing_account | Billing account id on which ot assign billing roles. | string | ✓ | | -| name | Playground folder name. | string | ✓ | | -| organization_id | Top-level organization id on which to apply roles, format is the numeric id. | number | ✓ | | -| parent | Parent organization or folder, in organizations/nnn or folders/nnn format. | string | ✓ | | -| *administrators* | List of IAM-style identities that will manage the playground. | list(string) | | [] | -| *billing_roles* | List of IAM roles granted to administrators on the billing account. | list(string) | | ... | -| *folder_roles* | List of IAM roles granted to administrators on folder. | list(string) | | ... | -| *organization_roles* | List of IAM roles granted to administrators on the organization. | list(string) | | ... | - -## Outputs - -| name | description | sensitive | -|---|---|:---:| -| folder | Folder resource. | | -| folder_name | Folder name. | | - diff --git a/modules/__sandbox/playground/main.tf b/modules/__sandbox/playground/main.tf deleted file mode 100644 index 233ec2a788..0000000000 --- a/modules/__sandbox/playground/main.tf +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright 2019 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 { - iam_billing_pairs = { - for pair in setproduct(var.billing_roles, var.administrators) : - join("-", pair) => { role = pair.0, member = pair.1 } - } - iam_organization_pairs = { - for pair in setproduct(var.organization_roles, var.administrators) : - join("-", pair) => { role = pair.0, member = pair.1 } - } -} - -resource "google_folder" "folder" { - provider = google-beta - display_name = var.name - parent = var.parent -} - -resource "google_folder_iam_binding" "authoritative" { - for_each = toset(var.folder_roles) - provider = google-beta - folder = google_folder.folder.name - role = each.value - members = var.administrators -} - -resource "google_billing_account_iam_member" "non_authoritative" { - for_each = local.iam_billing_pairs - billing_account_id = var.billing_account - role = each.value.role - member = each.value.member -} - -resource "google_organization_iam_member" "non_authoritative" { - for_each = local.iam_organization_pairs - org_id = var.organization_id - role = each.value.role - member = each.value.member -} diff --git a/modules/__sandbox/playground/variables.tf b/modules/__sandbox/playground/variables.tf deleted file mode 100644 index e28f811590..0000000000 --- a/modules/__sandbox/playground/variables.tf +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2019 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. - */ - -variable "administrators" { - description = "List of IAM-style identities that will manage the playground." - type = list(string) - default = [] -} - -variable "billing_account" { - description = "Billing account id on which ot assign billing roles." - type = string -} - -variable "name" { - description = "Playground folder name." - type = string -} - -variable "billing_roles" { - description = "List of IAM roles granted to administrators on the billing account." - type = list(string) - default = [ - "roles/billing.user" - ] -} - -variable "folder_roles" { - description = "List of IAM roles granted to administrators on folder." - type = list(string) - default = [ - "roles/resourcemanager.folderAdmin", - "roles/resourcemanager.projectCreator", - "roles/resourcemanager.projectIamAdmin", - "roles/compute.xpnAdmin" - ] -} - -variable "organization_id" { - description = "Top-level organization id on which to apply roles, format is the numeric id." - type = number -} - -variable "organization_roles" { - description = "List of IAM roles granted to administrators on the organization." - type = list(string) - default = [ - "roles/browser", - "roles/resourcemanager.organizationViewer" - ] -} - -variable "parent" { - description = "Parent organization or folder, in organizations/nnn or folders/nnn format." - type = string -}