main.tf (273 lines of code) (raw):

/** * Copyright 2021 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. */ # some of the resource does not support destroy so create unit id for name_prefix resource "random_string" "id" { length = 4 upper = false special = false } resource "time_static" "activation_date" {} resource "random_string" "shared_secret" { length = 40 lower = true special = false } locals { ba_roles = ["roles/backupdr.computeEngineOperator", "roles/logging.logWriter", "roles/iam.serviceAccountUser"] key_ring_roles = ["roles/cloudkms.cryptoKeyEncrypterDecrypter", "roles/cloudkms.admin"] enable_services = ["cloudkms.googleapis.com", "cloudresourcemanager.googleapis.com", "compute.googleapis.com", "iam.googleapis.com", "logging.googleapis.com"] profiles = { STANDARD_FOR_COMPUTE_ENGINE_VMS = { boot_disk_type = "pd-standard" boot_disk_size = "200" snap_pool_disk_size = "10" primary_pool_disk_size = "200" machine_type = "e2-standard-4" }, STANDARD_FOR_DATABASES_VMWARE_VMS = { boot_disk_type = "pd-balanced" boot_disk_size = "200" snap_pool_disk_size = "4096" primary_pool_disk_size = "200" machine_type = "n2-standard-16" } } } locals { timestamp_sanitized = sum([time_static.activation_date.unix, 86400]) shared_secret = "${random_string.shared_secret.result}00000000${format("%x", local.timestamp_sanitized)}" ba_service_account = var.create_ba_service_account ? join("", google_service_account.ba_service_account[*].email) : var.ba_service_account ba_randomised_name = join("-", tolist([var.ba_name, random_string.id.id])) ba_sa_randomised_name = join("-", [length(var.ba_name) + length(random_string.id.id) > 30 ? substr(var.ba_name, 0, 30 - length(random_string.id.id) - 1) : var.ba_name, random_string.id.id]) } # make sure the subnet exist. data "google_compute_subnetwork" "ba_subnet" { name = var.subnet project = var.vpc_host_project_id region = var.region lifecycle { postcondition { condition = self.private_ip_google_access == true error_message = "Make sure subnet exists and private_ip_google_access is required to be enabled!" } } } # Enable required services resource "google_project_service" "enable_services" { project = var.ba_project_id for_each = toset(local.enable_services) service = each.key disable_on_destroy = false depends_on = [data.google_compute_subnetwork.ba_subnet] } # create service account for BA appliance resource "google_service_account" "ba_service_account" { project = var.ba_project_id count = var.create_ba_service_account ? 1 : 0 account_id = local.ba_sa_randomised_name display_name = "Backup DR Appliance Service Account" depends_on = [google_project_service.enable_services] } # Assign the required permissions for BA Appliance service account. resource "google_project_iam_member" "ba_service_account_roles" { project = var.ba_project_id for_each = var.assign_roles_to_ba_sa ? toset(local.ba_roles) : [] member = "serviceAccount:${local.ba_service_account}" role = each.key depends_on = [google_project_service.enable_services] } # give BA service account permissions to access OnVault ba_vault_metadata resource "google_project_iam_member" "ba_service_vault_role" { project = var.ba_project_id count = var.assign_roles_to_ba_sa ? 1 : 0 role = "roles/backupdr.cloudStorageOperator" member = "serviceAccount:${local.ba_service_account}" condition { title = "${local.ba_randomised_name}-cloud-storage-metadata" description = "Permissions for storing GCE VM instance backup metadata in bucket" expression = "resource.name.startsWith(\"projects/_/buckets/${local.ba_randomised_name}\")" } } # create keyring and crypto keys resource "google_kms_key_ring" "ba_keyring" { project = var.ba_project_id location = var.region name = "${local.ba_randomised_name}-keyring" depends_on = [google_project_service.enable_services] } resource "google_kms_crypto_key" "kek" { key_ring = google_kms_key_ring.ba_keyring.id name = "kek" purpose = "ENCRYPT_DECRYPT" rotation_period = "7776000s" labels = var.labels version_template { algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION" protection_level = "SOFTWARE" } } # assign the permission for BA Appliance to access keyring resource "google_kms_key_ring_iam_member" "ba_keyring" { key_ring_id = google_kms_key_ring.ba_keyring.id for_each = toset(local.key_ring_roles) role = each.key member = "serviceAccount:${local.ba_service_account}" } # create compute resource for BA Appliance VM. resource "google_compute_disk" "boot_disk" { project = var.ba_project_id image = var.boot_image name = local.ba_randomised_name physical_block_size_bytes = 4096 size = local.profiles[var.ba_appliance_type].boot_disk_size type = local.profiles[var.ba_appliance_type].boot_disk_type zone = var.zone labels = var.labels depends_on = [google_project_service.enable_services] } resource "google_compute_disk" "ba_snapshot_pool" { project = var.ba_project_id name = "${local.ba_randomised_name}-snapshot-pool" physical_block_size_bytes = 4096 size = local.profiles[var.ba_appliance_type].snap_pool_disk_size type = local.profiles[var.ba_appliance_type].boot_disk_type zone = var.zone labels = var.labels depends_on = [google_project_service.enable_services] } resource "google_compute_disk" "ba_primary_pool" { project = var.ba_project_id name = "${local.ba_randomised_name}-primary-pool" physical_block_size_bytes = 4096 size = local.profiles[var.ba_appliance_type].primary_pool_disk_size type = local.profiles[var.ba_appliance_type].boot_disk_type zone = var.zone labels = var.labels depends_on = [google_project_service.enable_services] } resource "google_compute_attached_disk" "primary-pool" { project = var.ba_project_id disk = google_compute_disk.ba_primary_pool.id instance = google_compute_instance.appliance.id device_name = "primary-pool" } resource "google_compute_attached_disk" "snapshot-pool" { project = var.ba_project_id disk = google_compute_disk.ba_snapshot_pool.id instance = google_compute_instance.appliance.id device_name = "snapshot-pool" } resource "google_compute_instance" "appliance" { project = var.ba_project_id boot_disk { auto_delete = true device_name = local.ba_randomised_name source = google_compute_disk.boot_disk.id } deletion_protection = true machine_type = local.profiles[var.ba_appliance_type].machine_type metadata = { performance_pool_device = "snapshot-pool" primary_pool_device = "primary-pool" kms_keyringname = google_kms_key_ring.ba_keyring.name bootstrap_secret = local.shared_secret bucket_prefix = local.ba_randomised_name agm_instance_ivp_urls = local.ivp_urls_string } name = local.ba_randomised_name network_interface { subnetwork = data.google_compute_subnetwork.ba_subnet.name subnetwork_project = var.vpc_host_project_id } service_account { email = local.ba_service_account scopes = ["https://www.googleapis.com/auth/cloud-platform"] } shielded_instance_config { enable_integrity_monitoring = true enable_secure_boot = true enable_vtpm = true } zone = var.zone lifecycle { ignore_changes = [attached_disk, metadata] } labels = var.labels tags = var.network_tags depends_on = [google_project_service.enable_services, google_service_account.ba_service_account, data.http.fetch_ivp_urls] } # create firewall for the MC to communicate with BA appliance. resource "google_compute_firewall" "ba_firewall_rule" { project = var.vpc_host_project_id count = length(var.firewall_source_ip_ranges) > 0 ? 1 : 0 allow { ports = ["26", "443", "3260", "5107"] protocol = "tcp" } direction = "INGRESS" name = "${local.ba_randomised_name}-firewall-rule" network = var.network priority = 1000 source_ranges = var.firewall_source_ip_ranges target_service_accounts = [local.ba_service_account] depends_on = [google_compute_instance.appliance] } ### register BA appliance to management_server_endpoint data "google_client_config" "default" { count = var.ba_registration ? 1 : 0 } data "google_client_config" "fetch_ivp_urls" { } # call list management server. If baProxyUri exists then fetch the ivp urls for non psa deployments data "http" "fetch_ivp_urls" { url = "https://backupdr.googleapis.com/v1/projects/${var.ms_project_id}/locations/-/managementServers" method = "GET" request_headers = { Authorization = "Bearer ${data.google_client_config.fetch_ivp_urls.access_token}" } } locals { response_data_list_management_servers = jsondecode(data.http.fetch_ivp_urls.response_body) hasBAProxyUris = can(local.response_data_list_management_servers.managementServers[0].baProxyUri) ivp_url_string_1 = local.hasBAProxyUris ? local.response_data_list_management_servers.managementServers[0].baProxyUri[0] : null ivp_url_string_2 = local.hasBAProxyUris ? local.response_data_list_management_servers.managementServers[0].baProxyUri[1] : null ivp_urls_string = local.hasBAProxyUris ? join(",", [local.ivp_url_string_1, local.ivp_url_string_2]) : null } data "http" "actifio_session" { count = var.ba_registration ? 1 : 0 url = "${var.management_server_endpoint}/session" method = "POST" request_headers = { Authorization = "Bearer ${data.google_client_config.default[count.index].access_token}" } lifecycle { postcondition { condition = contains([200, 201, 204], self.status_code) error_message = "Actifio Session status code invalid. Make sure Management Server Configuration is correct!" } } depends_on = [google_compute_instance.appliance, google_compute_attached_disk.snapshot-pool, google_compute_attached_disk.primary-pool] } # tflint-ignore: terraform_unused_declarations data "http" "actifio_register" { count = var.ba_registration ? 1 : 0 url = "${var.management_server_endpoint}/cluster/register" method = "POST" request_headers = { Content-Type = "application/json" Authorization = "Bearer ${data.google_client_config.default[count.index].access_token}" backupdr-management-session = "Actifio ${jsondecode(data.http.actifio_session[count.index].response_body).session_id}" } request_body = jsonencode({ "ipaddress" = google_compute_instance.appliance.network_interface[0].network_ip "shared_secret" = local.shared_secret "deployBaWithoutPsa" = local.hasBAProxyUris ? true : false "serviceaccount" = local.ba_service_account }) retry { attempts = 20 min_delay_ms = 120000 max_delay_ms = 180000 } depends_on = [data.http.actifio_session, data.http.fetch_ivp_urls] }