diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7f93968 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,68 @@ +name: "Test" + +on: + pull_request: + paths: + - "**/*.tf" + - "**/*.tfvars" + - ".github/workflows/test.yml" + workflow_dispatch: + +jobs: + terraform-test: + name: Test Terraform + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "~1.8.0" + + - name: Install TFLint + uses: terraform-linters/setup-tflint@v4 + with: + tflint_version: latest + + - name: Install Trivy + run: | + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + + - name: Run all tests + run: make test + + + - name: Summary + if: always() + run: | + echo "## Terraform Lint Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Modules Checked" >> $GITHUB_STEP_SUMMARY + modules=$(find . -name "*.tf" -type f | xargs dirname | sort -u) + echo "The following Terraform modules were validated:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + for module in $modules; do + echo "- \`$module\`" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Checks Performed" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Terraform Format Check" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Terraform Validation" >> $GITHUB_STEP_SUMMARY + echo "- ✅ TFLint Analysis" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Security Scan (Trivy)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Local Development" >> $GITHUB_STEP_SUMMARY + echo "Run the same checks locally with:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "make test # Run all tests" >> $GITHUB_STEP_SUMMARY + echo "make format # Auto-format files" >> $GITHUB_STEP_SUMMARY + echo "make scan # Run security scan" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index a2d9c02..8d8bafd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +.terraform/ +.terraform.lock.hcl + # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..9ee3ef0 --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,64 @@ +config { + # Set minimum Terraform version to support provider functions + terraform_version = "~> 1.8.0" +} + +plugin "terraform" { + enabled = true + preset = "recommended" + version = "0.9.1" + source = "github.com/terraform-linters/tflint-ruleset-terraform" +} + +plugin "google" { + enabled = true +} + +rule "terraform_comment_syntax" { + enabled = true +} + +rule "terraform_deprecated_index" { + enabled = true +} + +rule "terraform_deprecated_interpolation" { + enabled = false # Disable to allow provider function syntax +} + +rule "terraform_documented_outputs" { + enabled = true +} + +rule "terraform_documented_variables" { + enabled = true +} + +rule "terraform_naming_convention" { + enabled = true + format = "snake_case" +} + +rule "terraform_required_providers" { + enabled = true +} + +rule "terraform_required_version" { + enabled = true +} + +rule "terraform_standard_module_structure" { + enabled = true +} + +rule "terraform_typed_variables" { + enabled = true +} + +rule "terraform_unused_declarations" { + enabled = true +} + +rule "terraform_unused_required_providers" { + enabled = true +} \ No newline at end of file diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..d336173 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,12 @@ +# Trivy ignore file for Suga GCP Plugins +# https://aquasecurity.github.io/trivy/latest/docs/configuration/filtering/ + +# Example: Ignore specific checks that may not apply to plugin modules +# AVD-GCP-0011 # Uncomment to ignore specific GCP checks +# AVD-GCP-0001 # Uncomment to ignore public access warnings if intentional + +# Ignore directories +.git/ +.terraform/ +node_modules/ +security-reports/ \ No newline at end of file diff --git a/DEVELOPERS.md b/DEVELOPERS.md new file mode 100644 index 0000000..f8034aa --- /dev/null +++ b/DEVELOPERS.md @@ -0,0 +1,16 @@ +# Developer Guide + +## Prerequisites + +- [Terraform](https://www.terraform.io/downloads) >= 1.5.0 +- [Docker](https://www.docker.com/get-started) (for tflint and trivy) + +## Usage + +```bash +make format # Format all Terraform files +make test # Run all tests (format-check, validate, lint, scan) +make clean # Clean up temp files +``` + +Tools run in Docker containers - no local installation needed. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..562acdc --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.PHONY: help format lint clean +.DEFAULT_GOAL := help + +## TODO: include MEDIUM severity in security scanning. +TRIVY_SEVERITY := HIGH,CRITICAL + +help: ## Show available commands + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-10s %s\n", $$1, $$2}' + +trivy-severity: ## Output the Trivy severity levels for use in scripts + @echo $(TRIVY_SEVERITY) + +format: ## Format all Terraform files + @find . -name "*.tf" -type f | xargs dirname | sort -u | xargs -I {} terraform fmt {} + +format-check: ## Check formatting of all Terraform files + @echo "Checking format..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + terraform fmt -check=true -diff=true "$$dir" || exit 1; \ + done + +validate: ## Validate all Terraform files + @echo "Validating..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + echo " $$dir"; \ + cd "$$dir" && terraform init -backend=false -get=true -upgrade=false >/dev/null && terraform validate && cd - >/dev/null || exit 1; \ + done + +lint: ## Lint using tflint + @echo "Running tflint..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + echo " $$dir"; \ + docker run --rm -v "$$(pwd)/$$dir:/data" -t ghcr.io/terraform-linters/tflint --format=compact --minimum-failure-severity=error; \ + done + +scan: ## Run security scan using Trivy + @echo "Running security scan..." + @docker run --rm -v "$$(pwd):/work" -w /work ghcr.io/aquasecurity/trivy:latest config . --format=table --quiet --exit-code 1 --severity $(TRIVY_SEVERITY) + +test: format-check validate lint scan ## Run all tests: format-check, validate, lint, and scan + @echo "All tests passed!" + +clean: ## Clean up .terraform directories and temp files + @find . -type d -name ".terraform" -exec rm -rf {} + 2>/dev/null || true + @find . -name "*.tfplan" -delete 2>/dev/null || true + @find . -name "*.tfstate*" -delete 2>/dev/null || true + @find . -name ".terraform.lock.hcl" -delete 2>/dev/null || true \ No newline at end of file diff --git a/cdn/module/main.tf b/cdn/module/main.tf index 50c4905..a4fae4e 100644 --- a/cdn/module/main.tf +++ b/cdn/module/main.tf @@ -39,15 +39,15 @@ resource "google_project_service" "required_services" { service = each.key project = var.project_id # Leave API enabled on destroy - disable_on_destroy = false + disable_on_destroy = false disable_dependent_services = false } resource "random_string" "cdn_prefix" { length = 8 special = false - lower = true - upper = false + lower = true + upper = false } # Create Network Endpoint Groups for services @@ -63,7 +63,7 @@ resource "google_compute_region_network_endpoint_group" "service_negs" { service = each.value.id } - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } # Create Backend Services for services @@ -72,9 +72,9 @@ resource "google_compute_backend_service" "service_backends" { project = var.project_id - name = "${provider::corefunc::str_kebab(each.key)}-service-bs" - protocol = "HTTPS" - enable_cdn = false + name = "${provider::corefunc::str_kebab(each.key)}-service-bs" + protocol = "HTTPS" + enable_cdn = false backend { group = google_compute_region_network_endpoint_group.service_negs[each.key].self_link @@ -83,7 +83,7 @@ resource "google_compute_backend_service" "service_backends" { # Create a Global IP Address for the CDN resource "google_compute_global_address" "cdn_ip" { - name = "cdn-ip-${random_string.cdn_prefix.result}" + name = "cdn-ip-${random_string.cdn_prefix.result}" project = var.project_id } @@ -120,14 +120,14 @@ resource "google_compute_region_network_endpoint_group" "external_negs" { name = "${each.key}-external-neg" network_endpoint_type = "INTERNET_FQDN_PORT" - region = var.region + region = var.region connection { host = each.value.domain_name port = 443 } - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } resource "google_compute_backend_service" "external_backends" { @@ -135,9 +135,9 @@ resource "google_compute_backend_service" "external_backends" { project = var.project_id - name = "${provider::corefunc::str_kebab(each.key)}-external-bs" - protocol = "HTTPS" - enable_cdn = false + name = "${provider::corefunc::str_kebab(each.key)}-external-bs" + protocol = "HTTPS" + enable_cdn = false backend { group = google_compute_region_network_endpoint_group.external_negs[each.key].self_link @@ -146,17 +146,17 @@ resource "google_compute_backend_service" "external_backends" { locals { // Return correct backend_service depending on type of the default origin - default_origin_backend_service = (contains(keys(local.cloud_storage_origins), local.default_origin) ? - google_compute_backend_bucket.bucket_backends[local.default_origin].self_link : - (contains(keys(local.cloud_run_origins), local.default_origin) ? - google_compute_backend_service.service_backends[local.default_origin].self_link : - google_compute_backend_service.external_backends[local.default_origin].self_link)) + default_origin_backend_service = (contains(keys(local.cloud_storage_origins), local.default_origin) ? + google_compute_backend_bucket.bucket_backends[local.default_origin].self_link : + (contains(keys(local.cloud_run_origins), local.default_origin) ? + google_compute_backend_service.service_backends[local.default_origin].self_link : + google_compute_backend_service.external_backends[local.default_origin].self_link)) } # Create a URL Map for routing requests resource "google_compute_url_map" "https_url_map" { name = "https-site-url-map-${random_string.cdn_prefix.result}" - project = var.project_id + project = var.project_id default_service = local.default_origin_backend_service host_rule { @@ -173,13 +173,13 @@ resource "google_compute_url_map" "https_url_map" { content { service = google_compute_backend_service.service_backends[path_rule.key].self_link - paths = [ + paths = [ // The route path provided by the user may or may not start with a slash but will always end with a slash. // Ensure /${path}/* and /${path}/ are both supported regardless of what the base path is. startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}*" : "/${path_rule.value.base_path}${path_rule.value.path}*", // Ensure /${path}/* - startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ + startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ ] - + route_action { url_rewrite { path_prefix_rewrite = "/" @@ -194,13 +194,13 @@ resource "google_compute_url_map" "https_url_map" { content { service = google_compute_backend_bucket.bucket_backends[path_rule.key].self_link - paths = [ + paths = [ // The route path provided by the user may or may not start with a slash but will always end with a slash. // Ensure /${path}/* and /${path}/ are both supported regardless of what the base path is. startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}*" : "/${path_rule.value.base_path}${path_rule.value.path}*", // Ensure /${path}/* - startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ + startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ ] - + route_action { url_rewrite { path_prefix_rewrite = "/" @@ -214,13 +214,13 @@ resource "google_compute_url_map" "https_url_map" { content { service = google_compute_backend_service.external_backends[path_rule.key].self_link - paths = [ + paths = [ // The route path provided by the user may or may not start with a slash but will always end with a slash. // Ensure /${path}/* and /${path}/ are both supported regardless of what the base path is. startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}*" : "/${path_rule.value.base_path}${path_rule.value.path}*", // Ensure /${path}/* - startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ + startswith("${path_rule.value.base_path}${path_rule.value.path}", "/") ? "${path_rule.value.base_path}${path_rule.value.path}" : "/${path_rule.value.base_path}${path_rule.value.path}" // Ensure /${path}/ ] - + route_action { url_rewrite { path_prefix_rewrite = "/" @@ -233,10 +233,10 @@ resource "google_compute_url_map" "https_url_map" { # Lookup the Managed Zone for the CDN Domain data "google_dns_managed_zone" "cdn_zone" { - name = var.cdn_domain.zone_name + name = var.cdn_domain.zone_name project = var.project_id - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } # Create DNS Records for the CDN @@ -245,8 +245,8 @@ resource "google_dns_record_set" "cdn_dns_record" { managed_zone = data.google_dns_managed_zone.cdn_zone.name type = "A" rrdatas = [google_compute_global_address.cdn_ip.address] - ttl = var.cdn_domain.domain_ttl - project = var.project_id + ttl = var.cdn_domain.domain_ttl + project = var.project_id } resource "google_dns_record_set" "www_cdn_dns_record" { @@ -254,8 +254,8 @@ resource "google_dns_record_set" "www_cdn_dns_record" { managed_zone = data.google_dns_managed_zone.cdn_zone.name type = "A" rrdatas = [google_compute_global_address.cdn_ip.address] - ttl = var.cdn_domain.domain_ttl - project = var.project_id + ttl = var.cdn_domain.domain_ttl + project = var.project_id } resource "google_certificate_manager_certificate" "cdn_cert" { @@ -268,7 +268,7 @@ resource "google_certificate_manager_certificate" "cdn_cert" { domains = [var.cdn_domain.domain_name] } - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } resource "google_certificate_manager_certificate_map" "cdn_cert_map" { @@ -276,24 +276,24 @@ resource "google_certificate_manager_certificate_map" "cdn_cert_map" { name = "cert-map-${random_string.cdn_prefix.result}" description = "CDN Certificate Map" - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } resource "google_certificate_manager_certificate_map_entry" "cdn_cert_map_entry" { - name = "cdn-cert-map-entry-${random_string.cdn_prefix.result}" - description = "CDN Certificate Map Entry" - map = google_certificate_manager_certificate_map.cdn_cert_map.name + name = "cdn-cert-map-entry-${random_string.cdn_prefix.result}" + description = "CDN Certificate Map Entry" + map = google_certificate_manager_certificate_map.cdn_cert_map.name certificates = [google_certificate_manager_certificate.cdn_cert.id] - matcher = "PRIMARY" - project = var.project_id + matcher = "PRIMARY" + project = var.project_id } # Create a Target HTTPS Proxy resource "google_compute_target_https_proxy" "https_proxy" { - name = "https-proxy-${random_string.cdn_prefix.result}" + name = "https-proxy-${random_string.cdn_prefix.result}" certificate_map = "//certificatemanager.googleapis.com/${google_certificate_manager_certificate_map.cdn_cert_map.id}" - url_map = google_compute_url_map.https_url_map.self_link - project = var.project_id + url_map = google_compute_url_map.https_url_map.self_link + project = var.project_id } # Create a Global Forwarding Rule @@ -303,5 +303,5 @@ resource "google_compute_global_forwarding_rule" "https_forwarding_rule" { ip_protocol = "TCP" port_range = "443" target = google_compute_target_https_proxy.https_proxy.self_link - project = var.project_id + project = var.project_id } \ No newline at end of file diff --git a/cdn/module/variables.tf b/cdn/module/variables.tf index 42df35e..4502d82 100644 --- a/cdn/module/variables.tf +++ b/cdn/module/variables.tf @@ -3,12 +3,12 @@ variable "suga" { name = string stack_id = string origins = map(object({ - path = string - base_path = string - type = string + path = string + base_path = string + type = string domain_name = string - id = string - resources = map(string) + id = string + resources = map(string) })) }) } diff --git a/cloudrun/module/main.tf b/cloudrun/module/main.tf index 1302ec5..b14b77a 100644 --- a/cloudrun/module/main.tf +++ b/cloudrun/module/main.tf @@ -22,7 +22,7 @@ resource "google_project_service" "required_services" { service = each.key project = var.project_id # Leave API enabled on destroy - disable_on_destroy = false + disable_on_destroy = false disable_dependent_services = false } @@ -33,12 +33,12 @@ resource "google_artifact_registry_repository" "service-image-repo" { description = "service images for suga stack ${var.suga.name}" format = "DOCKER" - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } locals { artifact_registry_url = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.service-image-repo.name}" - service_image_url = "${local.artifact_registry_url}/${var.suga.name}" + service_image_url = "${local.artifact_registry_url}/${var.suga.name}" } # Tag the provided docker image with the repository url @@ -81,10 +81,10 @@ resource "random_password" "event_token" { resource "google_cloud_run_v2_service" "service" { name = replace(var.suga.name, "_", "-") - location = var.region - project = var.project_id - launch_stage = "GA" - ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + location = var.region + project = var.project_id + launch_stage = "GA" + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" deletion_protection = false @@ -93,7 +93,7 @@ resource "google_cloud_run_v2_service" "service" { min_instance_count = var.min_instances max_instance_count = var.max_instances } - + containers { image = "${local.service_image_url}@${docker_registry_image.push.sha256_digest}" resources { @@ -108,7 +108,7 @@ resource "google_cloud_run_v2_service" "service" { } env { - name = "SUGA_GUEST_PORT" + name = "SUGA_GUEST_PORT" value = 8080 } diff --git a/cloudrun/module/outputs.tf b/cloudrun/module/outputs.tf index 87147b3..e684c98 100644 --- a/cloudrun/module/outputs.tf +++ b/cloudrun/module/outputs.tf @@ -1,12 +1,12 @@ output "suga" { - value = { - id = google_cloud_run_v2_service.service.name - domain_name = google_cloud_run_v2_service.service.uri - exports = { - resources = { - "google_cloud_run_v2_service" = google_cloud_run_v2_service.service.name - } - } + value = { + id = google_cloud_run_v2_service.service.name + domain_name = google_cloud_run_v2_service.service.uri + exports = { + resources = { + "google_cloud_run_v2_service" = google_cloud_run_v2_service.service.name + } } + } } diff --git a/cloudrun/module/variables.tf b/cloudrun/module/variables.tf index 0f36f0e..cf55c99 100644 --- a/cloudrun/module/variables.tf +++ b/cloudrun/module/variables.tf @@ -1,9 +1,9 @@ variable "suga" { type = object({ - name = string - stack_id = string - image_id = string - env = map(string) + name = string + stack_id = string + image_id = string + env = map(string) identities = map(object({ exports = map(string) })) @@ -11,65 +11,65 @@ variable "suga" { } variable "environment" { - type = map(string) - description = "Environment variables to set on the lambda function" - default = {} + type = map(string) + description = "Environment variables to set on the lambda function" + default = {} } # TODO: review defaults variable "memory_mb" { - description = "The amount of memory to allocate to the CloudRun service in MB" - type = number - default = 512 + description = "The amount of memory to allocate to the CloudRun service in MB" + type = number + default = 512 } variable "cpus" { - description = "The amount of cpus to allocate to the CloudRun service" - type = number - default = 1 + description = "The amount of cpus to allocate to the CloudRun service" + type = number + default = 1 } variable "gpus" { - description = "The amount of gpus to allocate to the CloudRun service" - type = number - default = 0 + description = "The amount of gpus to allocate to the CloudRun service" + type = number + default = 0 } variable "min_instances" { - description = "The minimum number of instances to run" - type = number - default = 0 + description = "The minimum number of instances to run" + type = number + default = 0 } variable "max_instances" { - description = "The maximum number of instances to run" - type = number - default = 10 + description = "The maximum number of instances to run" + type = number + default = 10 } variable "container_concurrency" { - description = "The number of concurrent requests the CloudRun service can handle" - type = number - default = 80 + description = "The number of concurrent requests the CloudRun service can handle" + type = number + default = 80 } variable "timeout_seconds" { - description = "The timeout for the CloudRun service in seconds" - type = number - default = 10 + description = "The timeout for the CloudRun service in seconds" + type = number + default = 10 } variable "project_id" { - description = "The project ID to deploy the CloudRun service to" - type = string + description = "The project ID to deploy the CloudRun service to" + type = string } variable "region" { - description = "The region to deploy the CloudRun service to" - type = string + description = "The region to deploy the CloudRun service to" + type = string } variable "container_port" { - description = "The port to expose the CloudRun service to" - type = number - default = 9001 + description = "The port to expose the CloudRun service to" + type = number + default = 9001 } \ No newline at end of file diff --git a/serviceaccount/module/main.tf b/serviceaccount/module/main.tf index af92c3e..18f72fa 100644 --- a/serviceaccount/module/main.tf +++ b/serviceaccount/module/main.tf @@ -12,7 +12,7 @@ resource "google_project_service" "required_services" { service = each.key project = var.project_id # Leave API enabled on destroy - disable_on_destroy = false + disable_on_destroy = false disable_dependent_services = false } @@ -27,5 +27,5 @@ resource "google_service_account" "service_account" { display_name = "${var.suga.name} service account" description = "Service account which runs the ${var.suga.name}" - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } \ No newline at end of file diff --git a/serviceaccount/module/outputs.tf b/serviceaccount/module/outputs.tf index b0b2000..51de3bf 100644 --- a/serviceaccount/module/outputs.tf +++ b/serviceaccount/module/outputs.tf @@ -1,10 +1,10 @@ output "suga" { value = { exports = { - "gcp_service_account" = google_service_account.service_account.email - "gcp_service_account:id" = google_service_account.service_account.id + "gcp_service_account" = google_service_account.service_account.email + "gcp_service_account:id" = google_service_account.service_account.id "gcp_service_account:email" = google_service_account.service_account.email - "gcp_service_account:name" = google_service_account.service_account.display_name + "gcp_service_account:name" = google_service_account.service_account.display_name } } } diff --git a/serviceaccount/module/variables.tf b/serviceaccount/module/variables.tf index 9c8d967..309fd58 100644 --- a/serviceaccount/module/variables.tf +++ b/serviceaccount/module/variables.tf @@ -1,12 +1,12 @@ variable "suga" { type = object({ - name = string + name = string stack_id = string }) } variable "trusted_actions" { - type = list(string) + type = list(string) default = [ "monitoring.timeSeries.create", "resourcemanager.projects.get", diff --git a/storage/module/main.tf b/storage/module/main.tf index 964fb7e..60909e4 100644 --- a/storage/module/main.tf +++ b/storage/module/main.tf @@ -9,7 +9,7 @@ locals { locals { suga_bucket_name = provider::corefunc::str_kebab(var.suga.name) - bucket_name = "${local.suga_bucket_name}-${var.suga.stack_id}" + bucket_name = "${local.suga_bucket_name}-${var.suga.stack_id}" } # Enable the required services @@ -19,7 +19,7 @@ resource "google_project_service" "required_services" { service = each.key project = var.project_id # Leave API enabled on destroy - disable_on_destroy = false + disable_on_destroy = false disable_dependent_services = false } @@ -30,57 +30,57 @@ resource "google_storage_bucket" "bucket" { project = var.project_id storage_class = var.storage_class - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } locals { - read_actions = ["storage.objects.get", "storage.objects.list"] - write_actions = ["storage.objects.create", "storage.objects.delete"] + read_actions = ["storage.objects.get", "storage.objects.list"] + write_actions = ["storage.objects.create", "storage.objects.delete"] delete_actions = ["storage.objects.delete"] } resource "google_project_iam_custom_role" "bucket_access_role" { for_each = var.suga.services - role_id = "BucketAccess_${substr("${var.suga.name}_${each.key}", 0, 40)}_${var.suga.stack_id}" + role_id = "BucketAccess_${substr("${var.suga.name}_${each.key}", 0, 40)}_${var.suga.stack_id}" project = var.project_id title = "${each.key} Bucket Access For ${var.suga.name}" description = "Custom role that allows access to the ${var.suga.name} bucket" permissions = distinct(concat( - ["storage.buckets.list", "storage.buckets.get"], // Base roles required for finding buckets - contains(each.value.actions, "read") ? local.read_actions : [], - contains(each.value.actions, "write") ? local.write_actions : [], - contains(each.value.actions, "delete") ? local.delete_actions : [] + ["storage.buckets.list", "storage.buckets.get"], // Base roles required for finding buckets + contains(each.value.actions, "read") ? local.read_actions : [], + contains(each.value.actions, "write") ? local.write_actions : [], + contains(each.value.actions, "delete") ? local.delete_actions : [] ) ) - depends_on = [ google_project_service.required_services ] + depends_on = [google_project_service.required_services] } resource "google_project_iam_member" "iam_access" { for_each = var.suga.services project = var.project_id - role = google_project_iam_custom_role.bucket_access_role[each.key].name - member = "serviceAccount:${each.value.identities["gcp:iam:role"].exports["gcp_service_account:email"]}" + role = google_project_iam_custom_role.bucket_access_role[each.key].name + member = "serviceAccount:${each.value.identities["gcp:iam:role"].exports["gcp_service_account:email"]}" } locals { relative_content_path = "${path.root}/../../../${var.suga.content_path}" - content_files = var.suga.content_path != "" ? fileset(local.relative_content_path, "**/*") : [] + content_files = var.suga.content_path != "" ? fileset(local.relative_content_path, "**/*") : [] } # Upload each file to GCP cloud storage (only if files exist) resource "google_storage_bucket_object" "files" { for_each = toset(local.content_files) - + bucket = google_storage_bucket.bucket.name - name = each.value + name = each.value source = "${local.relative_content_path}/${each.value}" - + detect_md5hash = filemd5("${local.relative_content_path}/${each.value}") - + content_type = lookup({ "html" = "text/html" "css" = "text/css" diff --git a/storage/module/outputs.tf b/storage/module/outputs.tf index 8974ef8..6164a53 100644 --- a/storage/module/outputs.tf +++ b/storage/module/outputs.tf @@ -1,11 +1,11 @@ output "suga" { - value = { - id = google_storage_bucket.bucket.id - domain_name = google_storage_bucket.bucket.url - exports = { - resources = { - "google_storage_bucket" = google_storage_bucket.bucket.id - } - } + value = { + id = google_storage_bucket.bucket.id + domain_name = google_storage_bucket.bucket.url + exports = { + resources = { + "google_storage_bucket" = google_storage_bucket.bucket.id + } } + } } \ No newline at end of file diff --git a/storage/module/variables.tf b/storage/module/variables.tf index 6a664db..bd185e7 100644 --- a/storage/module/variables.tf +++ b/storage/module/variables.tf @@ -1,7 +1,7 @@ variable "suga" { type = object({ - name = string - stack_id = string + name = string + stack_id = string content_path = string services = map(object({ actions = list(string)