From 81a7fb8d1452e7d6fb4a46c782b44483f9279f80 Mon Sep 17 00:00:00 2001 From: sudharsane sivamany Date: Wed, 22 Oct 2025 20:15:57 +0530 Subject: [PATCH 1/4] feat: Added base module for fsdr --- README.md | 71 +++- examples/1-SWITCHOVER-ATP-OKE/README.md | 118 ------- examples/1-SWITCHOVER-ATP-OKE/execution.yaml | 21 -- examples/1-SWITCHOVER-ATP-OKE/locals.tf | 57 ---- examples/1-SWITCHOVER-ATP-OKE/region1.tf | 40 --- examples/1-SWITCHOVER-ATP-OKE/region1.yaml | 53 --- examples/1-SWITCHOVER-ATP-OKE/region2.tf | 36 -- examples/1-SWITCHOVER-ATP-OKE/region2.yaml | 51 --- examples/fsdr/README.md | 106 ++++++ examples/fsdr/main.tf | 29 ++ .../providers.tf | 3 +- examples/fsdr/region1.yaml | 43 +++ examples/fsdr/region2.yaml | 56 +++ outputs.tf | 33 ++ region1.tf | 73 ++++ region1_data.tf | 86 +++++ region2.tf | 71 ++++ region2_data.tf | 85 +++++ variables.tf | 323 ++++++++++++++++++ versions.tf | 14 + 20 files changed, 990 insertions(+), 379 deletions(-) delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/README.md delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/execution.yaml delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/locals.tf delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/region1.tf delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/region1.yaml delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/region2.tf delete mode 100644 examples/1-SWITCHOVER-ATP-OKE/region2.yaml create mode 100644 examples/fsdr/README.md create mode 100644 examples/fsdr/main.tf rename examples/{1-SWITCHOVER-ATP-OKE => fsdr}/providers.tf (99%) create mode 100644 examples/fsdr/region1.yaml create mode 100644 examples/fsdr/region2.yaml create mode 100644 outputs.tf create mode 100644 region1.tf create mode 100644 region1_data.tf create mode 100644 region2.tf create mode 100644 region2_data.tf create mode 100644 variables.tf create mode 100644 versions.tf diff --git a/README.md b/README.md index 83fbf35..df34c71 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,75 @@ examples folder contains fully-functional examples that you can copy and paste " - git is installed. - Terraform 1.3.0 or higher version is installed. +## Usage + +See the [examples/](./examples/fsdr/) folder for usage examples. + +```hcl +module "fsdr" { + source = "../../" + providers = { + oci.region1 = oci.region1 + oci.region2 = oci.region2 + } + region1_config = yamldecode(file("${path.module}/region1.yaml")) + region2_config = yamldecode(file("${path.module}/region2.yaml")) +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [oci](#requirement\_oci) | >= 5.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [oci.region1](#provider\_oci.region1) | >= 5.0.0 | +| [oci.region2](#provider\_oci.region2) | >= 5.0.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [dr\_plan\_execution\_region1](#module\_dr\_plan\_execution\_region1) | ./modules/dr_plan_and_execution | n/a | +| [dr\_plan\_execution\_region2](#module\_dr\_plan\_execution\_region2) | ./modules/dr_plan_and_execution | n/a | +| [dr\_plan\_region1](#module\_dr\_plan\_region1) | ./modules/dr_plan_and_execution | n/a | +| [dr\_plan\_region2](#module\_dr\_plan\_region2) | ./modules/dr_plan_and_execution | n/a | +| [dr\_protection\_group\_region1](#module\_dr\_protection\_group\_region1) | ./modules/dr_protection_group | n/a | +| [dr\_protection\_group\_region2](#module\_dr\_protection\_group\_region2) | ./modules/dr_protection_group | n/a | + +## Resources + +| Name | Type | +|------|------| +| [oci_disaster_recovery_dr_protection_group.region1_dr_protection_group](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/disaster_recovery_dr_protection_group) | data source | +| [oci_disaster_recovery_dr_protection_group.region2_dr_protection_group](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/disaster_recovery_dr_protection_group) | data source | +| [oci_disaster_recovery_dr_protection_groups.region1_dr_protection_groups](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/disaster_recovery_dr_protection_groups) | data source | +| [oci_disaster_recovery_dr_protection_groups.region2_dr_protection_groups](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/disaster_recovery_dr_protection_groups) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [region1\_config](#input\_region1\_config) | Region1 config is required. This is PRIMARY. |
object({
compartment_id = string
protection_group_display_name = string
peer_region = string
defined_tags = optional(map(string), null)
freeform_tags = optional(map(string), null)
log_location = object({
bucket = string
namespace = string
})
disassociate_trigger = optional(number, null)
add_members = optional(list(object({
autonomous_database_standby_type_for_dr_drills = optional(string, null)
member_id = string
member_type = string
bucket = optional(string, null)
connection_string_type = optional(string, null)
destination_availability_domain = optional(string, null)
destination_backup_policy_id = optional(string, null)
destination_capacity_reservation_id = optional(string, null)
destination_compartment_id = optional(string, null)
destination_dedicated_vm_host_id = optional(string, null)
destination_load_balancer_id = optional(string, null)
destination_network_load_balancer_id = optional(string, null)
destination_snapshot_policy_id = optional(string, null)
gtid_reconciliation_timeout = optional(number, 0)
is_continue_on_gtid_reconciliation_timeout = optional(bool, false)
is_movable = optional(bool, false)
is_retain_fault_domain = optional(bool, false)
is_start_stop_enabled = optional(bool, false)
jump_host_id = optional(string, null)
namespace = optional(string, null)
password_vault_secret_id = optional(string, null)
peer_cluster_id = optional(string, null)
peer_db_system_id = optional(string, null)
backend_set_mappings = optional(list(object({
destination_backend_set_name = optional(string, null)
is_backend_set_for_non_movable = optional(bool, false)
source_backend_set_name = optional(string, null)
})), [])
backup_config = optional(object({
backup_schedule = optional(string, null)
exclude_namespaces = optional(list(string), [])
image_replication_vault_secret_id = optional(string, null)
max_number_of_backups_retained = optional(number, null)
namespaces = optional(list(string), [])
replicate_images = optional(string, null)
}), null)
backup_location = optional(object({
bucket = optional(string, null)
namespace = optional(string, null)
}), null)
block_volume_attach_and_mount_operations = optional(object({
attachments = optional(list(object({
block_volume_id = optional(string, null)
volume_attachment_reference_instance_id = optional(string, null)
})), [])
mounts = optional(list(object({
mount_point = optional(string, null)
})), [])
}), {})
common_destination_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
db_system_admin_user_details = optional(object({
password_vault_secret_id = optional(string, null)
username = optional(string, null)
}), null)
db_system_replication_user_details = optional(object({
password_vault_secret_id = optional(string, null)
username = optional(string, null)
}), null)
destination_encryption_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
export_mappings = optional(list(object({
destination_mount_target_id = optional(string, null)
export_id = optional(string, null)
})), [])
file_system_operations = optional(list(object({
export_path = optional(string, null)
mount_details = optional(object({
mount_target_id = optional(string, null)
}), null)
unmount_details = optional(object({
unmount_target_id = optional(string, null)
}), null)
mount_point = optional(string, null)
mount_target_id = optional(string, null)
})), [])
load_balancer_mappings = optional(list(object({
destination_load_balancer_id = optional(string, null)
source_load_balancer_id = optional(string, null)
})), [])
managed_node_pool_configs = optional(list(object({
id = optional(string, null)
maximum = optional(number, null)
minimum = optional(number, null)
})), [])
network_load_balancer_mappings = optional(list(object({
destination_network_load_balancer_id = optional(string, null)
source_network_load_balancer_id = optional(string, null)
})), [])
source_volume_to_destination_encryption_key_mappings = optional(list(object({
source_volume_id = optional(string, null)
destination_encryption_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
})), [])
vault_mappings = optional(list(object({
destination_vault_id = optional(string, null)
source_vault_id = optional(string, null)
})), [])
virtual_node_pool_configs = optional(list(object({
id = optional(string, null)
maximum = optional(number, null)
minimum = optional(number, null)
})), [])
vnic_mapping = optional(list(object({
destination_nsg_id_list = optional(list(string), [])
destination_subnet_id = optional(string, null)
source_vnic_id = optional(string, null)
})), [])
vnic_mappings = optional(list(object({
destination_nsg_id_list = optional(list(string), [])
destination_primary_private_ip_address = optional(string, null)
destination_primary_private_ip_hostname_label = optional(string, null)
destination_reserved_public_ip_id = optional(string, null)
destination_subnet_id = optional(string, null)
source_vnic_id = optional(string, null)
})), [])
})), [])

remove_members = optional(list(string), [])
dr_plan_and_execution = optional(list(object({
plan_display_name = string
type = string
defined_tags = optional(map(string), null)
freeform_tags = optional(map(string), null)
source_plan_id = optional(string, null)
refresh_trigger = optional(number, null)
verify_trigger = optional(number, null)
plan_execution = optional(list(object({
plan_execution_type = string
are_prechecks_enabled = optional(bool, false)
are_warnings_ignored = optional(bool, false)
defined_tags = optional(map(string), null)
execution_display_name = string
freeform_tags = optional(map(string), null)
timeouts_create = optional(string, "20m")
timeouts_update = optional(string, "20m")
})), [])
})), [])
})
| n/a | yes | +| [region2\_config](#input\_region2\_config) | Region2 config is required. This is STANDBY. |
object({
compartment_id = string
protection_group_display_name = string
defined_tags = optional(map(string), null)
freeform_tags = optional(map(string), null)
log_location = object({
bucket = string
namespace = string
})
disassociate_trigger = optional(number, null)
add_members = optional(list(object({
autonomous_database_standby_type_for_dr_drills = optional(string, null)
member_id = string
member_type = string
bucket = optional(string, null)
connection_string_type = optional(string, null)
destination_availability_domain = optional(string, null)
destination_backup_policy_id = optional(string, null)
destination_capacity_reservation_id = optional(string, null)
destination_compartment_id = optional(string, null)
destination_dedicated_vm_host_id = optional(string, null)
destination_load_balancer_id = optional(string, null)
destination_network_load_balancer_id = optional(string, null)
destination_snapshot_policy_id = optional(string, null)
gtid_reconciliation_timeout = optional(number, 0)
is_continue_on_gtid_reconciliation_timeout = optional(bool, false)
is_movable = optional(bool, false)
is_retain_fault_domain = optional(bool, false)
is_start_stop_enabled = optional(bool, false)
jump_host_id = optional(string, null)
namespace = optional(string, null)
password_vault_secret_id = optional(string, null)
peer_cluster_id = optional(string, null)
peer_db_system_id = optional(string, null)
backend_set_mappings = optional(list(object({
destination_backend_set_name = optional(string, null)
is_backend_set_for_non_movable = optional(bool, false)
source_backend_set_name = optional(string, null)
})), [])
backup_config = optional(object({
backup_schedule = optional(string, null)
exclude_namespaces = optional(list(string), [])
image_replication_vault_secret_id = optional(string, null)
max_number_of_backups_retained = optional(number, null)
namespaces = optional(list(string), [])
replicate_images = optional(string, null)
}), null)
backup_location = optional(object({
bucket = optional(string, null)
namespace = optional(string, null)
}), null)
block_volume_attach_and_mount_operations = optional(object({
attachments = optional(list(object({
block_volume_id = optional(string, null)
volume_attachment_reference_instance_id = optional(string, null)
})), [])
mounts = optional(list(object({
mount_point = optional(string, null)
})), [])
}), {})
common_destination_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
db_system_admin_user_details = optional(object({
password_vault_secret_id = optional(string, null)
username = optional(string, null)
}), null)
db_system_replication_user_details = optional(object({
password_vault_secret_id = optional(string, null)
username = optional(string, null)
}), null)
destination_encryption_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
export_mappings = optional(list(object({
destination_mount_target_id = optional(string, null)
export_id = optional(string, null)
})), [])
file_system_operations = optional(list(object({
export_path = optional(string, null)
mount_details = optional(object({
mount_target_id = optional(string, null)
}), null)
unmount_details = optional(object({
unmount_target_id = optional(string, null)
}), null)
mount_point = optional(string, null)
mount_target_id = optional(string, null)
})), [])
load_balancer_mappings = optional(list(object({
destination_load_balancer_id = optional(string, null)
source_load_balancer_id = optional(string, null)
})), [])
managed_node_pool_configs = optional(list(object({
id = optional(string, null)
maximum = optional(number, null)
minimum = optional(number, null)
})), [])
network_load_balancer_mappings = optional(list(object({
destination_network_load_balancer_id = optional(string, null)
source_network_load_balancer_id = optional(string, null)
})), [])
source_volume_to_destination_encryption_key_mappings = optional(list(object({
source_volume_id = optional(string, null)
destination_encryption_key = optional(object({
encryption_key_id = optional(string, null)
vault_id = optional(string, null)
}), null)
})), [])
vault_mappings = optional(list(object({
destination_vault_id = optional(string, null)
source_vault_id = optional(string, null)
})), [])
virtual_node_pool_configs = optional(list(object({
id = optional(string, null)
maximum = optional(number, null)
minimum = optional(number, null)
})), [])
vnic_mapping = optional(list(object({
destination_nsg_id_list = optional(list(string), [])
destination_subnet_id = optional(string, null)
source_vnic_id = optional(string, null)
})), [])
vnic_mappings = optional(list(object({
destination_nsg_id_list = optional(list(string), [])
destination_primary_private_ip_address = optional(string, null)
destination_primary_private_ip_hostname_label = optional(string, null)
destination_reserved_public_ip_id = optional(string, null)
destination_subnet_id = optional(string, null)
source_vnic_id = optional(string, null)
})), [])
})), [])
remove_members = optional(list(string), [])
dr_plan_and_execution = optional(list(object({
plan_display_name = string
type = string
defined_tags = optional(map(string), null)
freeform_tags = optional(map(string), null)
source_plan_id = optional(string, null)
refresh_trigger = optional(number, null)
verify_trigger = optional(number, null)
plan_execution = optional(list(object({
plan_execution_type = string
are_prechecks_enabled = optional(bool, false)
are_warnings_ignored = optional(bool, false)
defined_tags = optional(map(string), null)
execution_display_name = string
freeform_tags = optional(map(string), null)
timeouts_create = optional(string, "20m")
timeouts_update = optional(string, "20m")
})), [])
})), [])
})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [region1\_dr\_protection\_group\_id](#output\_region1\_dr\_protection\_group\_id) | Protection group OCID of region1 | +| [region1\_members](#output\_region1\_members) | Members of the existing protection group in region1 | +| [region1\_plan\_ids](#output\_region1\_plan\_ids) | OCID of region1 plans | +| [region2\_dr\_protection\_group\_id](#output\_region2\_dr\_protection\_group\_id) | Protection group OCID of region2 | +| [region2\_members](#output\_region2\_members) | Members of the existing protection group in region2 | +| [region2\_plan\_ids](#output\_region2\_plan\_ids) | OCID of region2 plans | + ## Contributing This project is open source. Oracle appreciates any contributions that are made by the open source community. @@ -30,4 +99,4 @@ Copyright (c) 2025, Oracle and/or its affiliates. Licensed under the Universal Permissive License 1.0 or Apache License 2.0. -See [LICENSE](./LICENSE) for more details. +See [LICENSE](./LICENSE) for more details. \ No newline at end of file diff --git a/examples/1-SWITCHOVER-ATP-OKE/README.md b/examples/1-SWITCHOVER-ATP-OKE/README.md deleted file mode 100644 index bafaf86..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# 1-SWITCHOVER-ATP_OKE - -This module is an opinionated approach of creating DR protection group, plan and execution to conduct SWITCHOVER activity. - -## NOTE: -This example is for adding ATP DB's and OKE clusters only. - -## STEPS TO FOLLOW: - -Step1: Clone the repository and navigate to examples/1-SWITCHOVER-ATP-OKE. - -Step2: In the providers.tf, specify the region1 and region2 region identifiers. In this example, we use "us-phoenix-1" as region 1 and "eu-frankfurt-1" as region 2. Update your region identifiers accordingly. - -Step3: If you chose to use "SECURITY_TOKEN" as auth method , please do "oci session authenticate" for region1 and region2 region and have it in separate profile. EX: PHX and FRA. - -```bash -oci session authenticate -``` - -If you use other forms of authentication as explained here https://docs.oracle.com/en-us/iaas/Content/dev/terraform/configuring.htm, make sure to update the providers.tf accordingly. - -Step4: On the region1.yaml and region2.yaml ,under region1_dr_pg and region2_dr_pg update the compartment ocid, autonomous Database details and OKE details(namespace which needs to be switchovered). If you want to use any other configuration for ATP and OKE, you should modify those properties accordingly. - -Step5: Initialize the terraform, - -```bash -terraform init -``` - -Step6: Verify the plan - -```bash -terraform plan -``` - -Step7: Apply the changes, this will create the DR protection group in both region1 and region2, associate region2(standby) protection group to region1(primary), add members. - -```bash -terraform apply -auto-approve -``` - -Step8: Change the dr_plan_setup_on_region2 option in execution.yaml as 'true', Run terraform apply. This will create a built in default dr plan for the members added in the region2. - -Step9: Increment the value of 'region2_plan_execution' to '1' in execution.yaml, Run terraform apply. This will start the execution of SWITCHOVER in the region2. SWITCHOVER will happen from REGION1 to REGION2. - -In case if you want to run the Prechecks for the SWITCHOVER plan, please change the plan_execution_type to 'SWITCHOVER_PRECHECK' in region2.yaml file. Once the prechecks are completed,change the plan_execution_type to "SWITCHOVER". It is highly recommended to run the prechecks first before running the SWITCHOVER plan. - -After the successful completition of SWITCHOVER plan, DRPG in region 2 will become primary and DRPG in region 1 will become standby. - -Step10: Now to start the SWITCHOVER from REGION2 to REGION1, change the execution.yaml as below - -```yaml -dr_plan_setup_on_region1: true -dr_plan_setup_on_region2: true - -Region_of_execution: REGION1 -``` - -Run terraform apply. This will create DR plan in the Region1. - -Step11: Increment the value of 'region1_plan_execution' to '1', Run terraform apply. This will start the execution of SWITCHOVER in the region1. Similar to Step 9, modify the plan_execution_type to 'SWITCHOVER_PRECHECK' and once the prechecks are completed flip back to 'SWITCHOVER_PRECHECK' - - -Going forward with the continuous SWITCHOVER operations, please change below value alone - -```yaml -Region_of_execution: REGION1 - -region1_refresh_trigger: 0 -region1_verify_trigger: 0 -region1_plan_execution: 0 - -region2_refresh_trigger: 0 -region2_verify_trigger: 0 -region2_plan_execution: 0 -``` - -For refresh and verify the plan, please increment the trigger values according. Best practice is to first increment the refresh trigger, then followed by verify trigger. - - -Step12: To destroy, increment the 'disassociate_trigger: 1' in region1.yaml for the actual primary group. Run terraform apply, this will disassociate teh standby protection group from primary. - -Step13: Run terraform destroy. - - -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.3.0 | -| [oci](#requirement\_oci) | >= 5.0.0 | - -## Providers - -No providers. - -## Modules - -| Name | Source | Version | -|------|--------|---------| -| [dr\_plan\_execution\_region1](#module\_dr\_plan\_execution\_region1) | oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution | n/a | -| [dr\_plan\_execution\_region2](#module\_dr\_plan\_execution\_region2) | oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution | n/a | -| [dr\_plan\_region1](#module\_dr\_plan\_region1) | oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution | n/a | -| [dr\_plan\_region2](#module\_dr\_plan\_region2) | oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution | n/a | -| [dr\_protection\_group\_region1](#module\_dr\_protection\_group\_region1) | oracle-terraform-modules/fsdr/oci//modules/dr-protection-group | n/a | -| [dr\_protection\_group\_region2](#module\_dr\_protection\_group\_region2) | oracle-terraform-modules/fsdr/oci//modules/dr-protection-group | n/a | - -## Resources - -No resources. - -## Inputs - -No inputs. - -## Outputs - -No outputs. \ No newline at end of file diff --git a/examples/1-SWITCHOVER-ATP-OKE/execution.yaml b/examples/1-SWITCHOVER-ATP-OKE/execution.yaml deleted file mode 100644 index 794457a..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/execution.yaml +++ /dev/null @@ -1,21 +0,0 @@ -#// Copyright (c) 2025, Oracle and/or its affiliates. -#// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - - -################################ -# Execution call # -################################ - -dr_plan_setup_on_region1: false -dr_plan_setup_on_region2: false - - -Region_of_execution: REGION2 - -region1_refresh_trigger: 0 -region1_verify_trigger: 0 -region1_plan_execution: 0 - -region2_refresh_trigger: 0 -region2_verify_trigger: 0 -region2_plan_execution: 0 \ No newline at end of file diff --git a/examples/1-SWITCHOVER-ATP-OKE/locals.tf b/examples/1-SWITCHOVER-ATP-OKE/locals.tf deleted file mode 100644 index 6608d7e..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/locals.tf +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2025, Oracle and/or its affiliates. -// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - - -locals { - region_of_execution = yamldecode(file("${path.module}/execution.yaml"))["Region_of_execution"] - primary_members = [for member in yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["members"] : member] - altered_primary_members = [ - for entry in local.primary_members : { - autonomous_database_standby_type_for_dr_drills = try(entry.autonomous_database_standby_type_for_dr_drills, null) - member_id = entry.member_id - member_type = entry.member_type - connection_string_type = try(entry.connection_string_type, null) - gtid_reconciliation_timeout = try(entry.gtid_reconciliation_timeout, 0) - is_continue_on_gtid_reconciliation_timeout = try(entry.is_continue_on_gtid_reconciliation_timeout, false) - jump_host_id = try(entry.jump_host_id, null) - password_vault_secret_id = try(entry.password_vault_secret_id, null) - peer_cluster_id = try(entry.peer_cluster_id, null) - peer_db_system_id = try(entry.peer_db_system_id, null) - backup_config = local.region_of_execution != "REGION1" ? try(entry.backup_config, null) : null - backup_location = try(entry.backup_location, null) - db_system_admin_user_details = try(entry.db_system_admin_user_details, null) - db_system_replication_user_details = try(entry.db_system_replication_user_details, null) - destination_encryption_key = try(entry.destination_encryption_key, null) - load_balancer_mappings = try(entry.load_balancer_mappings, []) - managed_node_pool_configs = try(entry.managed_node_pool_configs, []) - network_load_balancer_mappings = try(entry.network_load_balancer_mappings, []) - vault_mappings = try(entry.vault_mappings, []) - virtual_node_pool_configs = try(entry.virtual_node_pool_configs, []) - } - ] - standby_members = [for member in yamldecode(file("${path.module}/region2.yaml"))["region2_dr_pg"]["members"] : member] - altered_standby_members = [ - for entry in local.standby_members : { - autonomous_database_standby_type_for_dr_drills = try(entry.autonomous_database_standby_type_for_dr_drills, null) - member_id = entry.member_id - member_type = entry.member_type - connection_string_type = try(entry.connection_string_type, null) - gtid_reconciliation_timeout = try(entry.gtid_reconciliation_timeout, 0) - is_continue_on_gtid_reconciliation_timeout = try(entry.is_continue_on_gtid_reconciliation_timeout, false) - jump_host_id = try(entry.jump_host_id, null) - password_vault_secret_id = try(entry.password_vault_secret_id, null) - peer_cluster_id = try(entry.peer_cluster_id, null) - peer_db_system_id = try(entry.peer_db_system_id, null) - backup_config = local.region_of_execution != "REGION2" ? try(entry.backup_config, null) : null - backup_location = try(entry.backup_location, null) - db_system_admin_user_details = try(entry.db_system_admin_user_details, null) - db_system_replication_user_details = try(entry.db_system_replication_user_details, null) - destination_encryption_key = try(entry.destination_encryption_key, null) - load_balancer_mappings = try(entry.load_balancer_mappings, []) - managed_node_pool_configs = try(entry.managed_node_pool_configs, []) - network_load_balancer_mappings = try(entry.network_load_balancer_mappings, []) - vault_mappings = try(entry.vault_mappings, []) - virtual_node_pool_configs = try(entry.virtual_node_pool_configs, []) - } - ] -} diff --git a/examples/1-SWITCHOVER-ATP-OKE/region1.tf b/examples/1-SWITCHOVER-ATP-OKE/region1.tf deleted file mode 100644 index 2e697be..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/region1.tf +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2025, Oracle and/or its affiliates. -// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - - -module "dr_protection_group_region1" { - providers = { - oci = oci.region1 - } - source = "oracle-terraform-modules/fsdr/oci//modules/dr-protection-group" - compartment_id = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["compartment_id"] - display_name = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["display_name"] - association = { role = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["association"]["role"] - peer_region = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["association"]["peer_region"] - peer_id = module.dr_protection_group_region2.dr_protection_group["id"] } - log_location = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["log_location"] - members = local.altered_primary_members - disassociate_trigger = yamldecode(file("${path.module}/region1.yaml"))["region1_dr_pg"]["disassociate_trigger"] -} - -module "dr_plan_region1" { - count = yamldecode(file("${path.module}/execution.yaml"))["dr_plan_setup_on_region1"] == true ? 1 : 0 - providers = { - oci = oci.region1 - } - source = "oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution" - dr_plan = merge(yamldecode(file("${path.module}/region1.yaml"))["dr_plan_region1"], - { dr_protection_group_id = module.dr_protection_group_region1.dr_protection_group["id"] - refresh_trigger = yamldecode(file("${path.module}/execution.yaml"))["region1_refresh_trigger"] - verify_trigger = yamldecode(file("${path.module}/execution.yaml"))["region1_verify_trigger"] }) -} - -module "dr_plan_execution_region1" { - providers = { - oci = oci.region1 - } - count = yamldecode(file("${path.module}/execution.yaml"))["region1_plan_execution"] - source = "oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution" - dr_plan_execution = merge(yamldecode(file("${path.module}/region1.yaml"))["dr_plan_execution_region1"], - { plan_id = module.dr_plan_region1[0].dr_plan["id"] }) -} diff --git a/examples/1-SWITCHOVER-ATP-OKE/region1.yaml b/examples/1-SWITCHOVER-ATP-OKE/region1.yaml deleted file mode 100644 index 47d8fe7..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/region1.yaml +++ /dev/null @@ -1,53 +0,0 @@ -#// Copyright (c) 2025, Oracle and/or its affiliates. -#// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - - -################# -# Region1 Setup # -################# - -# DG setup -region1_dr_pg: - compartment_id: "ocid1.compartment.oc1..xxx" # update dr compartment id - display_name: "dr-pg-test-phx" - log_location: - bucket: "bucket-dr-log-drtest" # update the bucket name - namespace: "XXX" # update the bucket namespace - association: - role: PRIMARY - peer_id: "dr-pg-test-fra" - peer_region: "eu-frankfurt-1" # set DR peer region - disassociate_trigger: null - members: - - autonomous_database_standby_type_for_dr_drills: "SNAPSHOT_STANDBY" - member_id: "ocid1.autonomousdatabase.oc1.phx.xxx" # update the atp db ocid - member_type: "AUTONOMOUS_DATABASE" - - member_id: "ocid1.cluster.oc1.phx.xxx" # update the oke ocid - member_type: "OKE_CLUSTER" - peer_cluster_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the peer oke ocid - backup_location: - bucket: "bucket-dr-backup-drtest" # update the bucket name - namespace: "XXX" # update the bucket namespace - backup_config: - backup_schedule: "FREQ=HOURLY;BYHOUR=0;BYMINUTE=0;INTERVAL=1" - exclude_namespaces: [] - image_replication_vault_secret_id: null - max_number_of_backups_retained: 3 - namespaces: ["ns1", "ns2"] # update the list of kubernetes namespace - replicate_images: "ENABLE" - - - - -# Plan Setup -dr_plan_region1: - display_name: Switchover-To-Phoenix - type: SWITCHOVER - dr_pg_display_name: "dr-pg-test-phx" - -# Execution Setup -dr_plan_execution_region1: - plan_execution_type: SWITCHOVER - are_prechecks_enabled: true - timeouts_create: 60m - timeouts_update: 60m \ No newline at end of file diff --git a/examples/1-SWITCHOVER-ATP-OKE/region2.tf b/examples/1-SWITCHOVER-ATP-OKE/region2.tf deleted file mode 100644 index c46346c..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/region2.tf +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2025, Oracle and/or its affiliates. -// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - - -module "dr_protection_group_region2" { - providers = { - oci = oci.region2 - } - source = "oracle-terraform-modules/fsdr/oci//modules/dr-protection-group" - compartment_id = yamldecode(file("${path.module}/region2.yaml"))["region2_dr_pg"]["compartment_id"] - display_name = yamldecode(file("${path.module}/region2.yaml"))["region2_dr_pg"]["display_name"] - log_location = yamldecode(file("${path.module}/region2.yaml"))["region2_dr_pg"]["log_location"] - members = local.altered_standby_members -} - -module "dr_plan_region2" { - count = yamldecode(file("${path.module}/execution.yaml"))["dr_plan_setup_on_region2"] == true ? 1 : 0 - providers = { - oci = oci.region2 - } - source = "oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution" - dr_plan = merge(yamldecode(file("${path.module}/region2.yaml"))["dr_plan_region2"], - { dr_protection_group_id = module.dr_protection_group_region2.dr_protection_group["id"] - refresh_trigger = yamldecode(file("${path.module}/execution.yaml"))["region2_refresh_trigger"] - verify_trigger = yamldecode(file("${path.module}/execution.yaml"))["region2_verify_trigger"] }) -} - -module "dr_plan_execution_region2" { - providers = { - oci = oci.region2 - } - count = yamldecode(file("${path.module}/execution.yaml"))["region2_plan_execution"] - source = "oracle-terraform-modules/fsdr/oci//modules/dr-plan-and-execution" - dr_plan_execution = merge(yamldecode(file("${path.module}/region2.yaml"))["dr_plan_execution_region2"], - { plan_id = module.dr_plan_region2[0].dr_plan["id"] }) -} diff --git a/examples/1-SWITCHOVER-ATP-OKE/region2.yaml b/examples/1-SWITCHOVER-ATP-OKE/region2.yaml deleted file mode 100644 index 8573542..0000000 --- a/examples/1-SWITCHOVER-ATP-OKE/region2.yaml +++ /dev/null @@ -1,51 +0,0 @@ -#// Copyright (c) 2025, Oracle and/or its affiliates. -#// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - -################# -# Region2 Setup # -################# - -# DG setup -region2_dr_pg: - compartment_id: "ocid1.compartment.oc1..xxx" # update dr compartment id - display_name: "dr-pg-test-fra" - log_location: - bucket: "bucket-dr-log-frankfurt" # update the bucket name - namespace: "XXX" # update the bucket namespace - disassociate_trigger: null - members: - - autonomous_database_standby_type_for_dr_drills: "REFRESHABLE_CLONE" - member_id: "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.yyy" # update the atp db ocid - member_type: "AUTONOMOUS_DATABASE" - - member_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the oke ocid - member_type: "OKE_CLUSTER" - peer_cluster_id: "ocid1.cluster.oc1.phx.xxx" # update the peer oke ocid - backup_location: - bucket: "bucket-oke-backup-frankfurt" # update the bucket name - namespace: "XXX" # update the bucket namespace - backup_config: - backup_schedule: "FREQ=HOURLY;BYHOUR=0;BYMINUTE=0;INTERVAL=1" - exclude_namespaces: [] - image_replication_vault_secret_id: null - max_number_of_backups_retained: 3 - namespaces: ["ns1", "ns2"] # update the list of kubernetes namespace - replicate_images: "ENABLE" - - - -# Plan Setup -dr_plan_region2: - display_name: Switchover-To-Frankfurt - type: SWITCHOVER - dr_pg_display_name: "dr-pg-test-fra" - -# Execution Setup -dr_plan_execution_region2: - plan_execution_type: SWITCHOVER - are_prechecks_enabled: true - timeouts_create: 60m - timeouts_update: 60m - - - - diff --git a/examples/fsdr/README.md b/examples/fsdr/README.md new file mode 100644 index 0000000..6ee2b66 --- /dev/null +++ b/examples/fsdr/README.md @@ -0,0 +1,106 @@ +# FSDR + +The module is for creating full stack disaster recovery protection group, plan and execution. + +Step1: Initialize the terraform + +```bash +terraform init +``` + +Step2: Update the region1.yaml(primary) and region2.yaml(standby) with necesary config. + +region1.yaml remove the empty set of add_member and add the members to the primary. + +```yaml +add_members: +- autonomous_database_standby_type_for_dr_drills: "SNAPSHOT_STANDBY" + member_id: "ocid1.autonomousdatabase.oc1.phx.xxx" # update the atp db ocid + member_type: "AUTONOMOUS_DATABASE" +- member_id: "ocid1.cluster.oc1.phx.xxx" # update the oke ocid + member_type: "OKE_CLUSTER" + peer_cluster_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the peer oke ocid + backup_location: + bucket: "bucket-dr-backup-drtest" # update the bucket name + namespace: "XXX" # update the bucket namespace + backup_config: + backup_schedule: "FREQ=HOURLY;BYHOUR=0;BYMINUTE=0;INTERVAL=1" + exclude_namespaces: [] + image_replication_vault_secret_id: null + max_number_of_backups_retained: 3 + namespaces: ["ns1", "ns2"] # update the list of kubernetes namespace + replicate_images: "ENABLE" +- member_id: ocid1.volumegroup.oc1.phx.xxx + member_type: VOLUME_GROUP +``` + +region2.yaml remove the empty set of add_member and add the members to the standby. Also in standby, don't need to add backup config for OKE_CLUSTER member type. + +```yaml +add_members: +- member_id: "ocid1.cluster.oc1.eu-frankfurt-1.aaaaaaaanb6wiykmpym2oem2phf463hjg4stewqaxp5ewmkpbchqjximn55a" # update the oke ocid + member_type: "OKE_CLUSTER" + peer_cluster_id: "ocid1.cluster.oc1.phx.aaaaaaaap67t4qjlkavn2ohs3pcf44nrfxcyzlkmaibd25mnrcd7eukmkcnq" # update the peer oke ocid + backup_location: + bucket: "bucket-oke-backup-frankfurt" # update the bucket name + namespace: "iotdev" # update the bucket namespace +- autonomous_database_standby_type_for_dr_drills: "REFRESHABLE_CLONE" + member_id: "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.antheljr7ewzdsqauw7c7vivrfwzravr6ypwudezf4u5gn3si4pgnf5ykzvq" # update the atp db ocid + member_type: "AUTONOMOUS_DATABASE" +``` + +###NOTE: + +add_member: option is used for adding newmbers and modufying existing members. Also once the member is added/modified by running terraform apply, for the next run members has to be removed from the yaml. + +remove_members: option is used for removing the existing meber from the protection group.adding the member_id list will remove it from the protection group. + +region1.yaml +```yaml +remove_members: +- "ocid1.cluster.oc1.phx.xxx" +- "ocid1.autonomousdatabase.oc1.phx.xxx" +``` + +region2.yaml +```yaml +remove_members: +- "ocid1.cluster.oc1.eu-frankfurt-1.yyy" +- "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.yyy" +``` +Step3: Apply the config for creating protection group and adding member to it. + +```bash +terraform apply +``` + +Step4: To create dr plan in standby(region2) add the plans as list. And run terraform apply. + +region2.yaml +```yaml +dr_plan_and_execution: +- plan_display_name: SWITCHOVER-TO-FRANKFURT + type: SWITCHOVER + refresh_trigger: null + verify_trigger: null + plan_execution: [] +``` + +Step5: To create the execution of plan, update the list and run terraform apply. + +region2.yaml +```yaml +dr_plan_and_execution: +- plan_display_name: SWITCHOVER-TO-FRANKFURT + type: SWITCHOVER + refresh_trigger: null + verify_trigger: null + plan_execution: + - execution_display_name: SWITCHOVER-1 + plan_execution_type: SWITCHOVER + timeouts_create: 60m +``` + +With this the SWITCHOVER is completed, Now the region2(PRIMARY) and region1(STANDBY) + +Step6: To create a plan and execution from region1(STANDBY), follow step 4 and 5 in region1.yaml. diff --git a/examples/fsdr/main.tf b/examples/fsdr/main.tf new file mode 100644 index 0000000..a2857ff --- /dev/null +++ b/examples/fsdr/main.tf @@ -0,0 +1,29 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +module "fsdr" { + source = "../../" + providers = { + oci.region1 = oci.region1 + oci.region2 = oci.region2 + } + region1_config = yamldecode(file("${path.module}/region1.yaml")) + region2_config = yamldecode(file("${path.module}/region2.yaml")) +} + +output "region1_members" { + value = yamlencode(module.fsdr.region1_members) +} + +output "region2_members" { + value = yamlencode(module.fsdr.region2_members) +} + +output "region1_plan_ids" { + value = module.fsdr.region1_plan_ids +} + +output "region2_plan_ids" { + value = module.fsdr.region2_plan_ids +} diff --git a/examples/1-SWITCHOVER-ATP-OKE/providers.tf b/examples/fsdr/providers.tf similarity index 99% rename from examples/1-SWITCHOVER-ATP-OKE/providers.tf rename to examples/fsdr/providers.tf index bb7ab8e..08defa8 100644 --- a/examples/1-SWITCHOVER-ATP-OKE/providers.tf +++ b/examples/fsdr/providers.tf @@ -1,7 +1,6 @@ // Copyright (c) 2025, Oracle and/or its affiliates. // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl - provider "oci" { alias = "region1" region = "us-phoenix-1" @@ -24,4 +23,4 @@ terraform { } } required_version = ">= 1.3.0" -} \ No newline at end of file +} diff --git a/examples/fsdr/region1.yaml b/examples/fsdr/region1.yaml new file mode 100644 index 0000000..358a574 --- /dev/null +++ b/examples/fsdr/region1.yaml @@ -0,0 +1,43 @@ +# // Copyright (c) 2025, Oracle and/or its affiliates. +# // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +################# +# Region1 Setup # +################# +compartment_id: "ocid1.compartment.oc1..xxx" # update dr compartment id +protection_group_display_name: "dr-pg-fsdr-phx" +log_location: + bucket: "bucket-dr-log-drtest" # create and update the bucket name + namespace: "XXX" # update the bucket namespace +peer_region: "eu-frankfurt-1" +disassociate_trigger: null +add_members: [] +# - autonomous_database_standby_type_for_dr_drills: "SNAPSHOT_STANDBY" +# member_id: "ocid1.autonomousdatabase.oc1.phx.xxx" # update the atp db ocid +# member_type: "AUTONOMOUS_DATABASE" +# - member_id: "ocid1.cluster.oc1.phx.xxx" # update the oke ocid +# member_type: "OKE_CLUSTER" +# peer_cluster_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the peer oke ocid +# backup_location: +# bucket: "bucket-dr-backup-drtest" # update the bucket name +# namespace: "XXX" # update the bucket namespace +# backup_config: +# backup_schedule: "FREQ=HOURLY;BYHOUR=0;BYMINUTE=0;INTERVAL=1" +# exclude_namespaces: [] +# image_replication_vault_secret_id: null +# max_number_of_backups_retained: 3 +# namespaces: ["ns1", "ns2"] # update the list of kubernetes namespace +# replicate_images: "ENABLE" +# - member_id: ocid1.volumegroup.oc1.phx.xxx +# member_type: VOLUME_GROUP +remove_members: [] +dr_plan_and_execution: +# - plan_display_name: SWITCHOVER-TO-PHOENIX +# type: SWITCHOVER +# refresh_trigger: null +# verify_trigger: null +# # plan_execution: +# # - execution_display_name: SWITCHOVER-1 +# # plan_execution_type: SWITCHOVER +# # timeouts_create: 60m diff --git a/examples/fsdr/region2.yaml b/examples/fsdr/region2.yaml new file mode 100644 index 0000000..cc4adfa --- /dev/null +++ b/examples/fsdr/region2.yaml @@ -0,0 +1,56 @@ +# // Copyright (c) 2025, Oracle and/or its affiliates. +# // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +################# +# Region2 Setup # +################# +compartment_id: "ocid1.compartment.oc1..xxx" # update dr compartment id +protection_group_display_name: "dr-pg-fsdr-fra" +log_location: + bucket: "bucket-dr-log-frankfurt" # create and update the bucket name + namespace: "XXX" # update the bucket namespace +disassociate_trigger: null +add_members: [] +# - member_id: "ocid1.cluster.oc1.eu-frankfurt-1.aaaaaaaanb6wiykmpym2oem2phf463hjg4stewqaxp5ewmkpbchqjximn55a" # update the oke ocid +# member_type: "OKE_CLUSTER" +# peer_cluster_id: "ocid1.cluster.oc1.phx.aaaaaaaap67t4qjlkavn2ohs3pcf44nrfxcyzlkmaibd25mnrcd7eukmkcnq" # update the peer oke ocid +# backup_location: +# bucket: "bucket-oke-backup-frankfurt" # update the bucket name +# namespace: "iotdev" # update the bucket namespace +# backup_config: +# backup_schedule: "FREQ=HOURLY;BYHOUR=0;BYMINUTE=0;INTERVAL=1" +# exclude_namespaces: [] +# image_replication_vault_secret_id: null +# max_number_of_backups_retained: 3outout +# namespaces: ["dex-test", "microservice-ecp-sim"] # update the list of kubernetes namespace "dex-test", +# replicate_images: "ENABLE" +# - member_id: ocid1.volumegroup.oc1.eu-frankfurt-1.abtheljs3ixznqs7tulqxjy25hlqxf2jvyvbeym2x7ejrvupoxlfkjmbuvnq +# member_type: VOLUME_GROUP +# - autonomous_database_standby_type_for_dr_drills: "REFRESHABLE_CLONE" +# member_id: "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.antheljr7ewzdsqauw7c7vivrfwzravr6ypwudezf4u5gn3si4pgnf5ykzvq" # update the atp db ocid +# member_type: "AUTONOMOUS_DATABASE" +remove_members: [] +# - "ocid1.cluster.oc1.eu-frankfurt-1.aaaaaaaanb6wiykmpym2oem2phf463hjg4stewqaxp5ewmkpbchqjximn55a" +# - "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.antheljr7ewzdsqauw7c7vivrfwzravr6ypwudezf4u5gn3si4pgnf5ykzvq" +dr_plan_and_execution: +# - plan_display_name: SWITCHOVER-TO-FRANKFURT +# type: SWITCHOVER +# refresh_trigger: 1 +# verify_trigger: 1 +# plan_execution: +# - execution_display_name: SWITCHOVER-1 +# plan_execution_type: SWITCHOVER +# timeouts_create: 60m +# - execution_display_name: SWITCHOVER-2 +# plan_execution_type: SWITCHOVER_PRECHECK +# timeouts_create: 60m +# are_prechecks_enabled: false +# - plan_display_name: STARTDRILL-TO-FRANKFURT +# type: START_DRILL +# refresh_trigger: 1 +# verify_trigger: 1 +# plan_execution: [] + + + diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..1916446 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,33 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +output "region1_members" { + value = local.region1_members_after_structure + description = "Members of the existing protection group in region1" +} + +output "region2_members" { + value = local.region2_members_after_structure + description = "Members of the existing protection group in region2" +} + +output "region1_dr_protection_group_id" { + value = module.dr_protection_group_region1.dr_protection_group.id + description = "Protection group OCID of region1" +} + +output "region2_dr_protection_group_id" { + value = module.dr_protection_group_region2.dr_protection_group.id + description = "Protection group OCID of region2" +} + +output "region1_plan_ids" { + value = { for entry in var.region1_config["dr_plan_and_execution"] : entry["plan_display_name"] => module.dr_plan_region1[entry["plan_display_name"]].dr_plan["id"] } + description = "OCID of region1 plans" +} + +output "region2_plan_ids" { + value = { for entry in var.region2_config["dr_plan_and_execution"] : entry["plan_display_name"] => module.dr_plan_region2[entry["plan_display_name"]].dr_plan["id"] } + description = "OCID of region2 plans" +} diff --git a/region1.tf b/region1.tf new file mode 100644 index 0000000..77f8f0c --- /dev/null +++ b/region1.tf @@ -0,0 +1,73 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +module "dr_protection_group_region1" { + providers = { + oci = oci.region1 + } + source = "./modules/dr_protection_group" + compartment_id = var.region1_config["compartment_id"] + display_name = var.region1_config["protection_group_display_name"] + association = { role = "PRIMARY" + peer_region = var.region1_config["peer_region"] + peer_id = module.dr_protection_group_region2.dr_protection_group["id"] + } + defined_tags = var.region1_config["defined_tags"] + freeform_tags = var.region1_config["freeform_tags"] + log_location = var.region1_config["log_location"] + members = local.region1_altered_members + disassociate_trigger = var.region1_config["disassociate_trigger"] +} + +module "dr_plan_region1" { + providers = { + oci = oci.region1 + } + for_each = { for entry in var.region1_config["dr_plan_and_execution"] : entry.plan_display_name => entry } + source = "./modules/dr_plan_and_execution" + dr_plan = { + display_name = each.value["plan_display_name"] + dr_protection_group_id = module.dr_protection_group_region1.dr_protection_group["id"] + type = each.value["type"] + defined_tags = each.value["defined_tags"] + freeform_tags = each.value["freeform_tags"] + source_plan_id = each.value["source_plan_id"] + refresh_trigger = each.value["refresh_trigger"] + verify_trigger = each.value["verify_trigger"] + } +} + +locals { + region1_plan_execution = flatten([for entry in var.region1_config["dr_plan_and_execution"] : [for key in entry.plan_execution : { + index = "${entry.plan_display_name}=>${key.execution_display_name}" + execution_display_name = key.execution_display_name + plan_execution_type = key.plan_execution_type + are_prechecks_enabled = key.are_prechecks_enabled + are_warnings_ignored = key.are_warnings_ignored + plan_id = entry.plan_display_name + defined_tags = key.defined_tags + freeform_tags = key.freeform_tags + timeouts_create = key.timeouts_create + timeouts_update = key.timeouts_update + }]]) +} + +module "dr_plan_execution_region1" { + providers = { + oci = oci.region1 + } + for_each = { for entry in local.region1_plan_execution : entry.index => entry } + source = "./modules/dr_plan_and_execution" + dr_plan_execution = { + plan_execution_type = each.value["plan_execution_type"] + are_prechecks_enabled = each.value["are_prechecks_enabled"] + are_warnings_ignored = each.value["are_warnings_ignored"] + plan_id = module.dr_plan_region1[each.value["plan_id"]].dr_plan["id"] + defined_tags = each.value["defined_tags"] + display_name = each.value["execution_display_name"] + freeform_tags = each.value["freeform_tags"] + timeouts_create = each.value["timeouts_create"] + timeouts_update = each.value["timeouts_update"] + } +} diff --git a/region1_data.tf b/region1_data.tf new file mode 100644 index 0000000..3d1b75f --- /dev/null +++ b/region1_data.tf @@ -0,0 +1,86 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + + +data "oci_disaster_recovery_dr_protection_groups" "region1_dr_protection_groups" { + provider = oci.region1 + compartment_id = var.region1_config["compartment_id"] + display_name = var.region1_config["protection_group_display_name"] +} + +locals { + region1_get_protection_group = try(data.oci_disaster_recovery_dr_protection_groups.region1_dr_protection_groups.dr_protection_group_collection[0].items[0].id, null) + region1_get_members = local.region1_get_protection_group != null ? data.oci_disaster_recovery_dr_protection_group.region1_dr_protection_group[0].members : [] + region1_member_id = [for entry in local.region1_get_members : entry.member_id] + region1_modification_members = { for entry in var.region1_config["add_members"] : entry.member_id => entry } + region1_member_id_modification_members = [for entry in local.region1_modification_members : entry.member_id] + region1_member_index_modification = toset(setintersection(local.region1_member_id, local.region1_member_id_modification_members)) + region1_members_after_structure = [ + for entry in local.region1_get_members : { + autonomous_database_standby_type_for_dr_drills = try(entry.autonomous_database_standby_type_for_dr_drills, "") + member_id = entry.member_id + member_type = entry.member_type + bucket = try(entry.bucket, "") + connection_string_type = try(entry.connection_string_type, "") + destination_availability_domain = try(entry.destination_availability_domain, "") + destination_backup_policy_id = try(entry.destination_backup_policy_id, "") + destination_capacity_reservation_id = try(entry.destination_capacity_reservation_id, "") + destination_compartment_id = try(entry.destination_compartment_id, "") + destination_dedicated_vm_host_id = try(entry.destination_dedicated_vm_host_id, "") + destination_load_balancer_id = try(entry.destination_load_balancer_id, "") + destination_network_load_balancer_id = try(entry.destination_network_load_balancer_id, "") + destination_snapshot_policy_id = try(entry.destination_snapshot_policy_id, "") + gtid_reconciliation_timeout = try(entry.gtid_reconciliation_timeout, 0) + is_continue_on_gtid_reconciliation_timeout = try(entry.is_continue_on_gtid_reconciliation_timeout, false) + is_movable = try(entry.is_movable, false) + is_retain_fault_domain = try(entry.is_retain_fault_domain, false) + is_start_stop_enabled = try(entry.is_start_stop_enabled, false) + jump_host_id = try(entry.jump_host_id, "") + namespace = try(entry.namespace, "") + password_vault_secret_id = try(entry.password_vault_secret_id, "") + peer_cluster_id = try(entry.peer_cluster_id, "") + peer_db_system_id = try(entry.peer_db_system_id, "") + backend_set_mappings = try(entry.backend_set_mappings, []) + backup_config = try(length(entry.backup_config), 0) == 0 ? null : try(entry.backup_config[0], entry.backup_config) + backup_location = try(length(entry.backup_location), 0) == 0 ? null : try({ bucket = entry.backup_location[0]["bucket"], namespace = entry.backup_location[0]["namespace"] }, entry.backup_location) + block_volume_attach_and_mount_operations = try(length(entry.block_volume_attach_and_mount_operations), 0) == 0 ? null : try(entry.block_volume_attach_and_mount_operations[0], entry.block_volume_attach_and_mount_operations) + common_destination_key = try(length(entry.common_destination_key), 0) == 0 ? null : try(entry.common_destination_key[0], entry.common_destination_key) + db_system_admin_user_details = try(length(entry.db_system_admin_user_details), 0) == 0 ? null : try(entry.db_system_admin_user_details[0], entry.db_system_admin_user_details) + db_system_replication_user_details = try(length(entry.db_system_replication_user_details), 0) == 0 ? null : try(entry.db_system_replication_user_details[0], entry.db_system_replication_user_details) + destination_encryption_key = try(length(entry.destination_encryption_key), 0) == 0 ? null : try(entry.destination_encryption_key[0], entry.destination_encryption_key) + export_mappings = try(entry.export_mappings, []) + file_system_operations = [for entry in try(entry.file_system_operations, []) : + { + export_path = try(entry.export_path, null) + mount_details = try(length(entry.mount_details), 0) == 0 ? null : try(entry.mount_details[0], entry.mount_details) + unmount_details = try(length(entry.unmount_details), 0) == 0 ? null : try(entry.unmount_details[0], entry.unmount_details) + mount_point = try(entry.mount_point, null) + mount_target_id = try(entry.mount_target_id, null) + }] + load_balancer_mappings = try(entry.load_balancer_mappings, []) + managed_node_pool_configs = try(entry.managed_node_pool_configs, []) + network_load_balancer_mappings = try(entry.network_load_balancer_mappings, []) + source_volume_to_destination_encryption_key_mappings = [for entry in try(entry.source_volume_to_destination_encryption_key_mappings, []) : + { + source_volume_id = try(entry.source_volume_id, null) + destination_encryption_key = try(length(entry.destination_encryption_key), 0) == 0 ? null : try(entry.destination_encryption_key[0], entry.destination_encryption_key) + }] + vault_mappings = try(entry.vault_mappings, []) + virtual_node_pool_configs = try(entry.virtual_node_pool_configs, []) + vnic_mapping = try(entry.vnic_mapping, []) + vnic_mappings = try(entry.vnic_mappings, []) + } if !contains(var.region1_config["remove_members"], entry.member_id)] + + region1_modified_members = [for entry in local.region1_members_after_structure : try(local.region1_modification_members[entry.member_id], entry)] + region1_append_members = [for key, value in local.region1_modification_members : value if !contains(local.region1_member_index_modification, key)] + region1_altered_members = concat(local.region1_modified_members, local.region1_append_members) + +} + + +data "oci_disaster_recovery_dr_protection_group" "region1_dr_protection_group" { + count = local.region1_get_protection_group == null ? 0 : 1 + provider = oci.region1 + dr_protection_group_id = data.oci_disaster_recovery_dr_protection_groups.region1_dr_protection_groups.dr_protection_group_collection[0].items[0].id +} diff --git a/region2.tf b/region2.tf new file mode 100644 index 0000000..adcd66a --- /dev/null +++ b/region2.tf @@ -0,0 +1,71 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +module "dr_protection_group_region2" { + providers = { + oci = oci.region2 + } + source = "./modules/dr_protection_group" + compartment_id = var.region2_config["compartment_id"] + display_name = var.region2_config["protection_group_display_name"] + defined_tags = var.region2_config["defined_tags"] + disassociate_trigger = var.region2_config["disassociate_trigger"] + freeform_tags = var.region2_config["freeform_tags"] + log_location = var.region2_config["log_location"] + members = local.region2_altered_members +} + +module "dr_plan_region2" { + providers = { + oci = oci.region2 + } + for_each = { for entry in var.region2_config["dr_plan_and_execution"] : entry.plan_display_name => entry } + source = "./modules/dr_plan_and_execution" + dr_plan = { + display_name = each.value["plan_display_name"] + dr_protection_group_id = module.dr_protection_group_region2.dr_protection_group["id"] + type = each.value["type"] + defined_tags = each.value["defined_tags"] + freeform_tags = each.value["freeform_tags"] + source_plan_id = each.value["source_plan_id"] + refresh_trigger = each.value["refresh_trigger"] + verify_trigger = each.value["verify_trigger"] + } +} + +locals { + region2_plan_execution = flatten([for entry in var.region2_config["dr_plan_and_execution"] : [for key in entry.plan_execution : { + index = "${entry.plan_display_name}=>${key.execution_display_name}" + execution_display_name = key.execution_display_name + plan_execution_type = key.plan_execution_type + are_prechecks_enabled = key.are_prechecks_enabled + are_warnings_ignored = key.are_warnings_ignored + plan_id = entry.plan_display_name + defined_tags = key.defined_tags + freeform_tags = key.freeform_tags + timeouts_create = key.timeouts_create + timeouts_update = key.timeouts_update + }]]) + + +} + +module "dr_plan_execution_region2" { + providers = { + oci = oci.region2 + } + for_each = { for entry in local.region2_plan_execution : entry.index => entry } + source = "./modules/dr_plan_and_execution" + dr_plan_execution = { + plan_execution_type = each.value["plan_execution_type"] + are_prechecks_enabled = each.value["are_prechecks_enabled"] + are_warnings_ignored = each.value["are_warnings_ignored"] + plan_id = module.dr_plan_region2[each.value["plan_id"]].dr_plan["id"] + defined_tags = each.value["defined_tags"] + display_name = each.value["execution_display_name"] + freeform_tags = each.value["freeform_tags"] + timeouts_create = each.value["timeouts_create"] + timeouts_update = each.value["timeouts_update"] + } +} diff --git a/region2_data.tf b/region2_data.tf new file mode 100644 index 0000000..1935ff1 --- /dev/null +++ b/region2_data.tf @@ -0,0 +1,85 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +data "oci_disaster_recovery_dr_protection_groups" "region2_dr_protection_groups" { + provider = oci.region2 + compartment_id = var.region2_config["compartment_id"] + display_name = var.region2_config["protection_group_display_name"] +} + +locals { + region2_get_protection_group = try(data.oci_disaster_recovery_dr_protection_groups.region2_dr_protection_groups.dr_protection_group_collection[0].items[0].id, null) + region2_get_members = local.region2_get_protection_group != null ? data.oci_disaster_recovery_dr_protection_group.region2_dr_protection_group[0].members : [] + region2_member_id = [for entry in local.region2_get_members : entry.member_id] + region2_modification_members = { for entry in var.region2_config["add_members"] : entry.member_id => entry } + region2_member_id_modification_members = [for entry in local.region2_modification_members : entry.member_id] + region2_member_index_modification = toset(setintersection(local.region2_member_id, local.region2_member_id_modification_members)) + region2_members_after_structure = [ + for entry in local.region2_get_members : { + autonomous_database_standby_type_for_dr_drills = try(entry.autonomous_database_standby_type_for_dr_drills, "") + member_id = entry.member_id + member_type = entry.member_type + bucket = try(entry.bucket, "") + connection_string_type = try(entry.connection_string_type, "") + destination_availability_domain = try(entry.destination_availability_domain, "") + destination_backup_policy_id = try(entry.destination_backup_policy_id, "") + destination_capacity_reservation_id = try(entry.destination_capacity_reservation_id, "") + destination_compartment_id = try(entry.destination_compartment_id, "") + destination_dedicated_vm_host_id = try(entry.destination_dedicated_vm_host_id, "") + destination_load_balancer_id = try(entry.destination_load_balancer_id, "") + destination_network_load_balancer_id = try(entry.destination_network_load_balancer_id, "") + destination_snapshot_policy_id = try(entry.destination_snapshot_policy_id, "") + gtid_reconciliation_timeout = try(entry.gtid_reconciliation_timeout, 0) + is_continue_on_gtid_reconciliation_timeout = try(entry.is_continue_on_gtid_reconciliation_timeout, false) + is_movable = try(entry.is_movable, false) + is_retain_fault_domain = try(entry.is_retain_fault_domain, false) + is_start_stop_enabled = try(entry.is_start_stop_enabled, false) + jump_host_id = try(entry.jump_host_id, "") + namespace = try(entry.namespace, "") + password_vault_secret_id = try(entry.password_vault_secret_id, "") + peer_cluster_id = try(entry.peer_cluster_id, "") + peer_db_system_id = try(entry.peer_db_system_id, "") + backend_set_mappings = try(entry.backend_set_mappings, []) + backup_config = try(length(entry.backup_config), 0) == 0 ? null : try(entry.backup_config[0], entry.backup_config) + backup_location = try(length(entry.backup_location), 0) == 0 ? null : try({ bucket = entry.backup_location[0]["bucket"], namespace = entry.backup_location[0]["namespace"] }, entry.backup_location) + block_volume_attach_and_mount_operations = try(length(entry.block_volume_attach_and_mount_operations), 0) == 0 ? null : try(entry.block_volume_attach_and_mount_operations[0], entry.block_volume_attach_and_mount_operations) + common_destination_key = try(length(entry.common_destination_key), 0) == 0 ? null : try(entry.common_destination_key[0], entry.common_destination_key) + db_system_admin_user_details = try(length(entry.db_system_admin_user_details), 0) == 0 ? null : try(entry.db_system_admin_user_details[0], entry.db_system_admin_user_details) + db_system_replication_user_details = try(length(entry.db_system_replication_user_details), 0) == 0 ? null : try(entry.db_system_replication_user_details[0], entry.db_system_replication_user_details) + destination_encryption_key = try(length(entry.destination_encryption_key), 0) == 0 ? null : try(entry.destination_encryption_key[0], entry.destination_encryption_key) + export_mappings = try(entry.export_mappings, []) + file_system_operations = [for entry in try(entry.file_system_operations, []) : + { + export_path = try(entry.export_path, null) + mount_details = try(length(entry.mount_details), 0) == 0 ? null : try(entry.mount_details[0], entry.mount_details) + unmount_details = try(length(entry.unmount_details), 0) == 0 ? null : try(entry.unmount_details[0], entry.unmount_details) + mount_point = try(entry.mount_point, null) + mount_target_id = try(entry.mount_target_id, null) + }] + load_balancer_mappings = try(entry.load_balancer_mappings, []) + managed_node_pool_configs = try(entry.managed_node_pool_configs, []) + network_load_balancer_mappings = try(entry.network_load_balancer_mappings, []) + source_volume_to_destination_encryption_key_mappings = [for entry in try(entry.source_volume_to_destination_encryption_key_mappings, []) : + { + source_volume_id = try(entry.source_volume_id, null) + destination_encryption_key = try(length(entry.destination_encryption_key), 0) == 0 ? null : try(entry.destination_encryption_key[0], entry.destination_encryption_key) + }] + vault_mappings = try(entry.vault_mappings, []) + virtual_node_pool_configs = try(entry.virtual_node_pool_configs, []) + vnic_mapping = try(entry.vnic_mapping, []) + vnic_mappings = try(entry.vnic_mappings, []) + } if !contains(var.region2_config["remove_members"], entry.member_id)] + + region2_modified_members = [for entry in local.region2_members_after_structure : try(local.region2_modification_members[entry.member_id], entry)] + region2_append_members = [for key, value in local.region2_modification_members : value if !contains(local.region2_member_index_modification, key)] + region2_altered_members = concat(local.region2_modified_members, local.region2_append_members) + +} + + +data "oci_disaster_recovery_dr_protection_group" "region2_dr_protection_group" { + count = local.region2_get_protection_group == null ? 0 : 1 + provider = oci.region2 + dr_protection_group_id = data.oci_disaster_recovery_dr_protection_groups.region2_dr_protection_groups.dr_protection_group_collection[0].items[0].id +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..610da37 --- /dev/null +++ b/variables.tf @@ -0,0 +1,323 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +variable "region1_config" { + description = "Region1 config is required. This is PRIMARY." + type = object({ + compartment_id = string + protection_group_display_name = string + peer_region = string + defined_tags = optional(map(string), null) + freeform_tags = optional(map(string), null) + log_location = object({ + bucket = string + namespace = string + }) + disassociate_trigger = optional(number, null) + add_members = optional(list(object({ + autonomous_database_standby_type_for_dr_drills = optional(string, null) + member_id = string + member_type = string + bucket = optional(string, null) + connection_string_type = optional(string, null) + destination_availability_domain = optional(string, null) + destination_backup_policy_id = optional(string, null) + destination_capacity_reservation_id = optional(string, null) + destination_compartment_id = optional(string, null) + destination_dedicated_vm_host_id = optional(string, null) + destination_load_balancer_id = optional(string, null) + destination_network_load_balancer_id = optional(string, null) + destination_snapshot_policy_id = optional(string, null) + gtid_reconciliation_timeout = optional(number, 0) + is_continue_on_gtid_reconciliation_timeout = optional(bool, false) + is_movable = optional(bool, false) + is_retain_fault_domain = optional(bool, false) + is_start_stop_enabled = optional(bool, false) + jump_host_id = optional(string, null) + namespace = optional(string, null) + password_vault_secret_id = optional(string, null) + peer_cluster_id = optional(string, null) + peer_db_system_id = optional(string, null) + backend_set_mappings = optional(list(object({ + destination_backend_set_name = optional(string, null) + is_backend_set_for_non_movable = optional(bool, false) + source_backend_set_name = optional(string, null) + })), []) + backup_config = optional(object({ + backup_schedule = optional(string, null) + exclude_namespaces = optional(list(string), []) + image_replication_vault_secret_id = optional(string, null) + max_number_of_backups_retained = optional(number, null) + namespaces = optional(list(string), []) + replicate_images = optional(string, null) + }), null) + backup_location = optional(object({ + bucket = optional(string, null) + namespace = optional(string, null) + }), null) + block_volume_attach_and_mount_operations = optional(object({ + attachments = optional(list(object({ + block_volume_id = optional(string, null) + volume_attachment_reference_instance_id = optional(string, null) + })), []) + mounts = optional(list(object({ + mount_point = optional(string, null) + })), []) + }), {}) + common_destination_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + db_system_admin_user_details = optional(object({ + password_vault_secret_id = optional(string, null) + username = optional(string, null) + }), null) + db_system_replication_user_details = optional(object({ + password_vault_secret_id = optional(string, null) + username = optional(string, null) + }), null) + destination_encryption_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + export_mappings = optional(list(object({ + destination_mount_target_id = optional(string, null) + export_id = optional(string, null) + })), []) + file_system_operations = optional(list(object({ + export_path = optional(string, null) + mount_details = optional(object({ + mount_target_id = optional(string, null) + }), null) + unmount_details = optional(object({ + unmount_target_id = optional(string, null) + }), null) + mount_point = optional(string, null) + mount_target_id = optional(string, null) + })), []) + load_balancer_mappings = optional(list(object({ + destination_load_balancer_id = optional(string, null) + source_load_balancer_id = optional(string, null) + })), []) + managed_node_pool_configs = optional(list(object({ + id = optional(string, null) + maximum = optional(number, null) + minimum = optional(number, null) + })), []) + network_load_balancer_mappings = optional(list(object({ + destination_network_load_balancer_id = optional(string, null) + source_network_load_balancer_id = optional(string, null) + })), []) + source_volume_to_destination_encryption_key_mappings = optional(list(object({ + source_volume_id = optional(string, null) + destination_encryption_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + })), []) + vault_mappings = optional(list(object({ + destination_vault_id = optional(string, null) + source_vault_id = optional(string, null) + })), []) + virtual_node_pool_configs = optional(list(object({ + id = optional(string, null) + maximum = optional(number, null) + minimum = optional(number, null) + })), []) + vnic_mapping = optional(list(object({ + destination_nsg_id_list = optional(list(string), []) + destination_subnet_id = optional(string, null) + source_vnic_id = optional(string, null) + })), []) + vnic_mappings = optional(list(object({ + destination_nsg_id_list = optional(list(string), []) + destination_primary_private_ip_address = optional(string, null) + destination_primary_private_ip_hostname_label = optional(string, null) + destination_reserved_public_ip_id = optional(string, null) + destination_subnet_id = optional(string, null) + source_vnic_id = optional(string, null) + })), []) + })), []) + + remove_members = optional(list(string), []) + dr_plan_and_execution = optional(list(object({ + plan_display_name = string + type = string + defined_tags = optional(map(string), null) + freeform_tags = optional(map(string), null) + source_plan_id = optional(string, null) + refresh_trigger = optional(number, null) + verify_trigger = optional(number, null) + plan_execution = optional(list(object({ + plan_execution_type = string + are_prechecks_enabled = optional(bool, false) + are_warnings_ignored = optional(bool, false) + defined_tags = optional(map(string), null) + execution_display_name = string + freeform_tags = optional(map(string), null) + timeouts_create = optional(string, "20m") + timeouts_update = optional(string, "20m") + })), []) + })), []) + }) +} + +variable "region2_config" { + description = "Region2 config is required. This is STANDBY." + type = object({ + compartment_id = string + protection_group_display_name = string + defined_tags = optional(map(string), null) + freeform_tags = optional(map(string), null) + log_location = object({ + bucket = string + namespace = string + }) + disassociate_trigger = optional(number, null) + add_members = optional(list(object({ + autonomous_database_standby_type_for_dr_drills = optional(string, null) + member_id = string + member_type = string + bucket = optional(string, null) + connection_string_type = optional(string, null) + destination_availability_domain = optional(string, null) + destination_backup_policy_id = optional(string, null) + destination_capacity_reservation_id = optional(string, null) + destination_compartment_id = optional(string, null) + destination_dedicated_vm_host_id = optional(string, null) + destination_load_balancer_id = optional(string, null) + destination_network_load_balancer_id = optional(string, null) + destination_snapshot_policy_id = optional(string, null) + gtid_reconciliation_timeout = optional(number, 0) + is_continue_on_gtid_reconciliation_timeout = optional(bool, false) + is_movable = optional(bool, false) + is_retain_fault_domain = optional(bool, false) + is_start_stop_enabled = optional(bool, false) + jump_host_id = optional(string, null) + namespace = optional(string, null) + password_vault_secret_id = optional(string, null) + peer_cluster_id = optional(string, null) + peer_db_system_id = optional(string, null) + backend_set_mappings = optional(list(object({ + destination_backend_set_name = optional(string, null) + is_backend_set_for_non_movable = optional(bool, false) + source_backend_set_name = optional(string, null) + })), []) + backup_config = optional(object({ + backup_schedule = optional(string, null) + exclude_namespaces = optional(list(string), []) + image_replication_vault_secret_id = optional(string, null) + max_number_of_backups_retained = optional(number, null) + namespaces = optional(list(string), []) + replicate_images = optional(string, null) + }), null) + backup_location = optional(object({ + bucket = optional(string, null) + namespace = optional(string, null) + }), null) + block_volume_attach_and_mount_operations = optional(object({ + attachments = optional(list(object({ + block_volume_id = optional(string, null) + volume_attachment_reference_instance_id = optional(string, null) + })), []) + mounts = optional(list(object({ + mount_point = optional(string, null) + })), []) + }), {}) + common_destination_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + db_system_admin_user_details = optional(object({ + password_vault_secret_id = optional(string, null) + username = optional(string, null) + }), null) + db_system_replication_user_details = optional(object({ + password_vault_secret_id = optional(string, null) + username = optional(string, null) + }), null) + destination_encryption_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + export_mappings = optional(list(object({ + destination_mount_target_id = optional(string, null) + export_id = optional(string, null) + })), []) + file_system_operations = optional(list(object({ + export_path = optional(string, null) + mount_details = optional(object({ + mount_target_id = optional(string, null) + }), null) + unmount_details = optional(object({ + unmount_target_id = optional(string, null) + }), null) + mount_point = optional(string, null) + mount_target_id = optional(string, null) + })), []) + load_balancer_mappings = optional(list(object({ + destination_load_balancer_id = optional(string, null) + source_load_balancer_id = optional(string, null) + })), []) + managed_node_pool_configs = optional(list(object({ + id = optional(string, null) + maximum = optional(number, null) + minimum = optional(number, null) + })), []) + network_load_balancer_mappings = optional(list(object({ + destination_network_load_balancer_id = optional(string, null) + source_network_load_balancer_id = optional(string, null) + })), []) + source_volume_to_destination_encryption_key_mappings = optional(list(object({ + source_volume_id = optional(string, null) + destination_encryption_key = optional(object({ + encryption_key_id = optional(string, null) + vault_id = optional(string, null) + }), null) + })), []) + vault_mappings = optional(list(object({ + destination_vault_id = optional(string, null) + source_vault_id = optional(string, null) + })), []) + virtual_node_pool_configs = optional(list(object({ + id = optional(string, null) + maximum = optional(number, null) + minimum = optional(number, null) + })), []) + vnic_mapping = optional(list(object({ + destination_nsg_id_list = optional(list(string), []) + destination_subnet_id = optional(string, null) + source_vnic_id = optional(string, null) + })), []) + vnic_mappings = optional(list(object({ + destination_nsg_id_list = optional(list(string), []) + destination_primary_private_ip_address = optional(string, null) + destination_primary_private_ip_hostname_label = optional(string, null) + destination_reserved_public_ip_id = optional(string, null) + destination_subnet_id = optional(string, null) + source_vnic_id = optional(string, null) + })), []) + })), []) + remove_members = optional(list(string), []) + dr_plan_and_execution = optional(list(object({ + plan_display_name = string + type = string + defined_tags = optional(map(string), null) + freeform_tags = optional(map(string), null) + source_plan_id = optional(string, null) + refresh_trigger = optional(number, null) + verify_trigger = optional(number, null) + plan_execution = optional(list(object({ + plan_execution_type = string + are_prechecks_enabled = optional(bool, false) + are_warnings_ignored = optional(bool, false) + defined_tags = optional(map(string), null) + execution_display_name = string + freeform_tags = optional(map(string), null) + timeouts_create = optional(string, "20m") + timeouts_update = optional(string, "20m") + })), []) + })), []) + }) +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..bacb671 --- /dev/null +++ b/versions.tf @@ -0,0 +1,14 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl + + +terraform { + required_providers { + oci = { + source = "oracle/oci" + version = ">= 5.0.0" + configuration_aliases = [oci.region1, oci.region2] + } + } + required_version = ">= 1.3.0" +} From 52f3db6a44df1a7291d6aa4df47c648a2099d7d4 Mon Sep 17 00:00:00 2001 From: sudharsane sivamany Date: Wed, 22 Oct 2025 20:23:11 +0530 Subject: [PATCH 2/4] feat: Added base module for fsdr --- examples/fsdr/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/fsdr/README.md b/examples/fsdr/README.md index 6ee2b66..c2533d4 100644 --- a/examples/fsdr/README.md +++ b/examples/fsdr/README.md @@ -38,14 +38,14 @@ region2.yaml remove the empty set of add_member and add the members to the stand ```yaml add_members: -- member_id: "ocid1.cluster.oc1.eu-frankfurt-1.aaaaaaaanb6wiykmpym2oem2phf463hjg4stewqaxp5ewmkpbchqjximn55a" # update the oke ocid +- member_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the oke ocid member_type: "OKE_CLUSTER" peer_cluster_id: "ocid1.cluster.oc1.phx.aaaaaaaap67t4qjlkavn2ohs3pcf44nrfxcyzlkmaibd25mnrcd7eukmkcnq" # update the peer oke ocid backup_location: bucket: "bucket-oke-backup-frankfurt" # update the bucket name - namespace: "iotdev" # update the bucket namespace + namespace: "xxx" # update the bucket namespace - autonomous_database_standby_type_for_dr_drills: "REFRESHABLE_CLONE" - member_id: "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.antheljr7ewzdsqauw7c7vivrfwzravr6ypwudezf4u5gn3si4pgnf5ykzvq" # update the atp db ocid + member_id: "ocid1.autonomousdatabase.oc1.eu-frankfurt-1.yyy" # update the atp db ocid member_type: "AUTONOMOUS_DATABASE" ``` From 0ca123cc58e531252c7148f2f6dd13841d8df287 Mon Sep 17 00:00:00 2001 From: sudharsane sivamany Date: Wed, 22 Oct 2025 20:25:14 +0530 Subject: [PATCH 3/4] feat: Added base module for fsdr --- examples/fsdr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fsdr/README.md b/examples/fsdr/README.md index c2533d4..e432b0c 100644 --- a/examples/fsdr/README.md +++ b/examples/fsdr/README.md @@ -40,7 +40,7 @@ region2.yaml remove the empty set of add_member and add the members to the stand add_members: - member_id: "ocid1.cluster.oc1.eu-frankfurt-1.yyy" # update the oke ocid member_type: "OKE_CLUSTER" - peer_cluster_id: "ocid1.cluster.oc1.phx.aaaaaaaap67t4qjlkavn2ohs3pcf44nrfxcyzlkmaibd25mnrcd7eukmkcnq" # update the peer oke ocid + peer_cluster_id: "ocid1.cluster.oc1.phx.xxx" # update the peer oke ocid backup_location: bucket: "bucket-oke-backup-frankfurt" # update the bucket name namespace: "xxx" # update the bucket namespace From 4f2a9c309d1f86aff761d24c9a0ceae45e73fb48 Mon Sep 17 00:00:00 2001 From: sudharsane sivamany Date: Thu, 23 Oct 2025 13:04:12 +0530 Subject: [PATCH 4/4] feat: update the readme --- README.md | 21 ++++++++++----------- region1.tf | 6 +++--- region2.tf | 6 +++--- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index df34c71..1bef5e0 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,6 @@ module "fsdr" { region2_config = yamldecode(file("${path.module}/region2.yaml")) } ``` - - -## Requirements - | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.3.0 | @@ -49,12 +45,12 @@ module "fsdr" { | Name | Source | Version | |------|--------|---------| -| [dr\_plan\_execution\_region1](#module\_dr\_plan\_execution\_region1) | ./modules/dr_plan_and_execution | n/a | -| [dr\_plan\_execution\_region2](#module\_dr\_plan\_execution\_region2) | ./modules/dr_plan_and_execution | n/a | -| [dr\_plan\_region1](#module\_dr\_plan\_region1) | ./modules/dr_plan_and_execution | n/a | -| [dr\_plan\_region2](#module\_dr\_plan\_region2) | ./modules/dr_plan_and_execution | n/a | -| [dr\_protection\_group\_region1](#module\_dr\_protection\_group\_region1) | ./modules/dr_protection_group | n/a | -| [dr\_protection\_group\_region2](#module\_dr\_protection\_group\_region2) | ./modules/dr_protection_group | n/a | +| [dr\_plan\_execution\_region1](#module\_dr\_plan\_execution\_region1) | ./modules/dr-plan-and-execution | n/a | +| [dr\_plan\_execution\_region2](#module\_dr\_plan\_execution\_region2) | ./modules/dr-plan-and-execution | n/a | +| [dr\_plan\_region1](#module\_dr\_plan\_region1) | ./modules/dr-plan-and-execution | n/a | +| [dr\_plan\_region2](#module\_dr\_plan\_region2) | ./modules/dr-plan-and-execution | n/a | +| [dr\_protection\_group\_region1](#module\_dr\_protection\_group\_region1) | ./modules/dr-protection-group | n/a | +| [dr\_protection\_group\_region2](#module\_dr\_protection\_group\_region2) | ./modules/dr-protection-group | n/a | ## Resources @@ -83,6 +79,8 @@ module "fsdr" { | [region2\_members](#output\_region2\_members) | Members of the existing protection group in region2 | | [region2\_plan\_ids](#output\_region2\_plan\_ids) | OCID of region2 plans | + + ## Contributing This project is open source. Oracle appreciates any contributions that are made by the open source community. @@ -99,4 +97,5 @@ Copyright (c) 2025, Oracle and/or its affiliates. Licensed under the Universal Permissive License 1.0 or Apache License 2.0. -See [LICENSE](./LICENSE) for more details. \ No newline at end of file +See [LICENSE](./LICENSE) for more details.## Requirements + diff --git a/region1.tf b/region1.tf index 77f8f0c..6eed9e4 100644 --- a/region1.tf +++ b/region1.tf @@ -6,7 +6,7 @@ module "dr_protection_group_region1" { providers = { oci = oci.region1 } - source = "./modules/dr_protection_group" + source = "./modules/dr-protection-group" compartment_id = var.region1_config["compartment_id"] display_name = var.region1_config["protection_group_display_name"] association = { role = "PRIMARY" @@ -25,7 +25,7 @@ module "dr_plan_region1" { oci = oci.region1 } for_each = { for entry in var.region1_config["dr_plan_and_execution"] : entry.plan_display_name => entry } - source = "./modules/dr_plan_and_execution" + source = "./modules/dr-plan-and-execution" dr_plan = { display_name = each.value["plan_display_name"] dr_protection_group_id = module.dr_protection_group_region1.dr_protection_group["id"] @@ -58,7 +58,7 @@ module "dr_plan_execution_region1" { oci = oci.region1 } for_each = { for entry in local.region1_plan_execution : entry.index => entry } - source = "./modules/dr_plan_and_execution" + source = "./modules/dr-plan-and-execution" dr_plan_execution = { plan_execution_type = each.value["plan_execution_type"] are_prechecks_enabled = each.value["are_prechecks_enabled"] diff --git a/region2.tf b/region2.tf index adcd66a..9fbf00d 100644 --- a/region2.tf +++ b/region2.tf @@ -6,7 +6,7 @@ module "dr_protection_group_region2" { providers = { oci = oci.region2 } - source = "./modules/dr_protection_group" + source = "./modules/dr-protection-group" compartment_id = var.region2_config["compartment_id"] display_name = var.region2_config["protection_group_display_name"] defined_tags = var.region2_config["defined_tags"] @@ -21,7 +21,7 @@ module "dr_plan_region2" { oci = oci.region2 } for_each = { for entry in var.region2_config["dr_plan_and_execution"] : entry.plan_display_name => entry } - source = "./modules/dr_plan_and_execution" + source = "./modules/dr-plan-and-execution" dr_plan = { display_name = each.value["plan_display_name"] dr_protection_group_id = module.dr_protection_group_region2.dr_protection_group["id"] @@ -56,7 +56,7 @@ module "dr_plan_execution_region2" { oci = oci.region2 } for_each = { for entry in local.region2_plan_execution : entry.index => entry } - source = "./modules/dr_plan_and_execution" + source = "./modules/dr-plan-and-execution" dr_plan_execution = { plan_execution_type = each.value["plan_execution_type"] are_prechecks_enabled = each.value["are_prechecks_enabled"]