From 802947b045ad1acf65e2daf48eb1a6c321708dec Mon Sep 17 00:00:00 2001 From: Michal Maciejczak <127428486+mmaciejc@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:00:13 +0100 Subject: [PATCH] Fmc device ha pair (#226) adding support for HA Co-authored-by: kadadhic <113530197+kadadhic@users.noreply.github.com> Co-authored-by: rchrabas --- .gitignore | 3 +- docs/data-sources/device_ha_pair.md | 64 ++ docs/resources/device_ha_pair.md | 123 ++++ .../fmc_device_ha_pairs/data-source.tf | 3 + .../resources/fmc_device_ha_pair/resource.tf | 49 +- .../resources/fmc_device_ha_pairs/import.sh | 1 + .../resources/fmc_device_ha_pairs/resource.tf | 27 + gen/definitions/device_ha_pair.yaml | 277 ++++++++ .../data_source_fmc_device_ha_pair.go | 297 +++++++++ .../data_source_fmc_device_ha_pair_test.go | 174 +++++ internal/provider/model_fmc_device_ha_pair.go | 551 ++++++++++++++++ internal/provider/provider.go | 2 + .../provider/resource_fmc_device_ha_pair.go | 607 ++++++++++++++++++ .../resource_fmc_device_ha_pair_test.go | 154 +++++ 14 files changed, 2309 insertions(+), 23 deletions(-) create mode 100644 docs/data-sources/device_ha_pair.md create mode 100644 docs/resources/device_ha_pair.md create mode 100644 examples/data-sources/fmc_device_ha_pairs/data-source.tf create mode 100644 examples/resources/fmc_device_ha_pairs/import.sh create mode 100644 examples/resources/fmc_device_ha_pairs/resource.tf create mode 100644 gen/definitions/device_ha_pair.yaml create mode 100644 internal/provider/data_source_fmc_device_ha_pair.go create mode 100644 internal/provider/data_source_fmc_device_ha_pair_test.go create mode 100644 internal/provider/model_fmc_device_ha_pair.go create mode 100644 internal/provider/resource_fmc_device_ha_pair.go create mode 100644 internal/provider/resource_fmc_device_ha_pair_test.go diff --git a/.gitignore b/.gitignore index fad74c21..7d764a69 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,8 @@ website/node_modules .vscode/settings.json website/vendor - +vendor +running.sh # Test exclusions !command/test-fixtures/**/*.tfstate !command/test-fixtures/**/.terraform/ diff --git a/docs/data-sources/device_ha_pair.md b/docs/data-sources/device_ha_pair.md new file mode 100644 index 00000000..61165206 --- /dev/null +++ b/docs/data-sources/device_ha_pair.md @@ -0,0 +1,64 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_ha_pair Data Source - terraform-provider-fmc" +subcategory: "Device" +description: |- + This data source can read the Device HA Pair. +--- + +# fmc_device_ha_pair (Data Source) + +This data source can read the Device HA Pair. + +## Example Usage + +```terraform +data "fmc_device_ha_pair" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} +``` + + +## Schema + +### Optional + +- `domain` (String) The name of the FMC domain +- `id` (String) The id of the object +- `name` (String) The name of the High Availability (HA) Pair. + +### Read-Only + +- `action` (String) FTD HA PUT operation action. Specifically used for manual switch. HA Break will be triggered when you run terraform destroy +- `encryption_enabled` (Boolean) Use encryption for communication. +- `encryption_key` (String) Pass shared key for encryption if CUSTOM key geneeration scheme is selected. +- `encryption_key_generation_scheme` (String) Select the encyption key generation scheme. +- `failed_interfaces_limit` (Number) Number of Failed Interfaces that triggers failover. +- `failed_interfaces_percent` (Number) Percentage of Failed Interfaces that triggers failover. +- `ha_link_interface_id` (String) ID of High Availability Link interface. +- `ha_link_interface_name` (String) Name of High Availability Link interface. +- `ha_link_interface_type` (String) Type of High Availability Link interface. +- `ha_link_logical_name` (String) The logical name of failover interface. +- `ha_link_netmask` (String) Subnet mask for HA link. +- `ha_link_primary_ip` (String) The IP of primary node interface. +- `ha_link_secondary_ip` (String) The IP of secondary node interface. +- `ha_link_use_ipv6` (Boolean) Use IPv6 addressing for HA communication. +- `interface_hold_time` (Number) Interface Hold Time in seconds +- `interface_poll_time` (Number) Peer Pool Time (1-15 SEC or 500-999 MSEC) +- `interface_poll_time_unit` (String) Peer Pool Time Unit +- `peer_hold_time` (Number) Peer Hold Time (3-45 SEC or 800-999 MSEC) +- `peer_hold_time_unit` (String) Peer Hold Time Unit +- `peer_poll_time` (Number) Peer Pool Time (1-15 SEC or 200-999 MSEC) +- `peer_poll_time_unit` (String) Peer Pool Time Unit +- `primary_device_id` (String) ID of primary FTD in the HA Pair. +- `secondary_device_id` (String) ID of secondary FTD in the HA Pair. +- `state_link_interface_id` (String) ID of physical interface. +- `state_link_interface_name` (String) Name of state link interface. +- `state_link_interface_type` (String) Type of state link interface. +- `state_link_logical_name` (String) +- `state_link_netmask` (String) Subnet mask for state link. +- `state_link_primary_ip` (String) The IP of primary node interface. +- `state_link_secondary_ip` (String) The IP of secondary node interface. +- `state_link_use_ipv6` (Boolean) Use IPv6 addressing for HA communication. +- `state_link_use_same_as_ha` (Boolean) Use the same link for state and HA. +- `type` (String) Type of the resource; This is always `DeviceHAPair`. diff --git a/docs/resources/device_ha_pair.md b/docs/resources/device_ha_pair.md new file mode 100644 index 00000000..7fb2efc8 --- /dev/null +++ b/docs/resources/device_ha_pair.md @@ -0,0 +1,123 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_ha_pair Resource - terraform-provider-fmc" +subcategory: "Device" +description: |- + Resource to manage HA Pair. failed_interfaces_limit or failed_interfaces_percent needs to be set. +--- + +# fmc_device_ha_pair (Resource) + +Resource to manage HA Pair. failed_interfaces_limit or failed_interfaces_percent needs to be set. + +## Example Usage + +```terraform +resource "fmc_device_ha_pair" "example" { + name = "FTD_HA" + primary_device_id = "76d24097-41c4-4558-a4d0-a8c07ac08470" + secondary_device_id = "96d24097-41c4-4332-a4d0-a8c07ac08482" + ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482" + ha_link_interface_name = "GigabitEthernet0/0" + ha_link_interface_type = "" + ha_link_logical_name = "LAN-INTERFACE" + ha_link_use_ipv6 = false + ha_link_primary_ip = "1.1.1.1" + ha_link_secondary_ip = "1.1.1.2" + ha_link_netmask = "255.255.255.0" + state_link_use_same_as_ha = false + state_link_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470" + state_link_interface_name = "GigabitEthernet0/0" + state_link_interface_type = "PhysicalInterface" + state_link_logical_name = "Stateful-INTERFACE" + state_link_use_ipv6 = false + state_link_primary_ip = "10.10.10.1" + state_link_secondary_ip = "10.10.10.2" + state_link_netmask = "255.255.255.0" + encryption_enabled = true + encryption_key_generation_scheme = "AUTO" + failed_interfaces_limit = 1 + peer_poll_time = 1 + peer_poll_time_unit = "SEC" + peer_hold_time = 15 + peer_hold_time_unit = "SEC" + interface_poll_time = 5 + interface_poll_time_unit = "SEC" + interface_hold_time = 25 +} +``` + + +## Schema + +### Required + +- `ha_link_interface_id` (String) ID of High Availability Link interface. +- `ha_link_interface_name` (String) Name of High Availability Link interface. +- `ha_link_interface_type` (String) Type of High Availability Link interface. +- `ha_link_logical_name` (String) The logical name of failover interface. +- `ha_link_netmask` (String) Subnet mask for HA link. +- `ha_link_primary_ip` (String) The IP of primary node interface. +- `ha_link_secondary_ip` (String) The IP of secondary node interface. +- `name` (String) The name of the High Availability (HA) Pair. +- `primary_device_id` (String) ID of primary FTD in the HA Pair. +- `secondary_device_id` (String) ID of secondary FTD in the HA Pair. +- `state_link_use_same_as_ha` (Boolean) Use the same link for state and HA. + +### Optional + +- `action` (String) FTD HA PUT operation action. Specifically used for manual switch. HA Break will be triggered when you run terraform destroy + - Choices: `SWITCH`, `HABREAK` +- `domain` (String) The name of the FMC domain +- `encryption_enabled` (Boolean) Use encryption for communication. +- `encryption_key` (String) Pass shared key for encryption if CUSTOM key geneeration scheme is selected. +- `encryption_key_generation_scheme` (String) Select the encyption key generation scheme. + - Choices: `AUTO`, `CUSTOM` +- `failed_interfaces_limit` (Number) Number of Failed Interfaces that triggers failover. + - Range: `1`-`211` +- `failed_interfaces_percent` (Number) Percentage of Failed Interfaces that triggers failover. + - Range: `1`-`100` +- `ha_link_use_ipv6` (Boolean) Use IPv6 addressing for HA communication. + - Default value: `false` +- `interface_hold_time` (Number) Interface Hold Time in seconds + - Range: `25`-`75` + - Default value: `25` +- `interface_poll_time` (Number) Peer Pool Time (1-15 SEC or 500-999 MSEC) + - Range: `1`-`999` + - Default value: `5` +- `interface_poll_time_unit` (String) Peer Pool Time Unit + - Choices: `SEC`, `MSEC` + - Default value: `SEC` +- `peer_hold_time` (Number) Peer Hold Time (3-45 SEC or 800-999 MSEC) + - Range: `3`-`999` + - Default value: `15` +- `peer_hold_time_unit` (String) Peer Hold Time Unit + - Choices: `SEC`, `MSEC` + - Default value: `SEC` +- `peer_poll_time` (Number) Peer Pool Time (1-15 SEC or 200-999 MSEC) + - Range: `1`-`999` + - Default value: `1` +- `peer_poll_time_unit` (String) Peer Pool Time Unit + - Choices: `SEC`, `MSEC` + - Default value: `SEC` +- `state_link_interface_id` (String) ID of physical interface. +- `state_link_interface_name` (String) Name of state link interface. +- `state_link_interface_type` (String) Type of state link interface. +- `state_link_logical_name` (String) +- `state_link_netmask` (String) Subnet mask for state link. +- `state_link_primary_ip` (String) The IP of primary node interface. +- `state_link_secondary_ip` (String) The IP of secondary node interface. +- `state_link_use_ipv6` (Boolean) Use IPv6 addressing for HA communication. + +### Read-Only + +- `id` (String) The id of the object +- `type` (String) Type of the resource; This is always `DeviceHAPair`. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import fmc_device_ha_pair.example "" +``` diff --git a/examples/data-sources/fmc_device_ha_pairs/data-source.tf b/examples/data-sources/fmc_device_ha_pairs/data-source.tf new file mode 100644 index 00000000..50f3b111 --- /dev/null +++ b/examples/data-sources/fmc_device_ha_pairs/data-source.tf @@ -0,0 +1,3 @@ +data "fmc_device_ha_pairs" "example" { + id = "76d24097-41c4-4558-a4d0-a8c07ac08470" +} diff --git a/examples/resources/fmc_device_ha_pair/resource.tf b/examples/resources/fmc_device_ha_pair/resource.tf index 62255af9..58eb7822 100644 --- a/examples/resources/fmc_device_ha_pair/resource.tf +++ b/examples/resources/fmc_device_ha_pair/resource.tf @@ -2,26 +2,31 @@ resource "fmc_device_ha_pair" "example" { name = "FTD_HA" primary_device_id = "76d24097-41c4-4558-a4d0-a8c07ac08470" secondary_device_id = "96d24097-41c4-4332-a4d0-a8c07ac08482" - is_encryption_enabled = false - use_same_link_for_failovers = false - shared_key = "cisco123" - enc_key_generation_scheme = "CUSTOM" - lan_failover_standby_ip = "1.1.1.2" - lan_failover_active_ip = "1.1.1.1" - lan_failover_name = "LAN-INTERFACE" - lan_failover_subnet_mask = "255.255.255.0" - lan_failover_ipv6_addr = false - lan_failover_interface_name = "GigabitEthernet0/0" - lan_failover_interface_id = "757kdgh5-41c4-4558-a4d0-a8c07ac08470" - lan_failover_interface_type = "PhysicalInterface" - stateful_failover_standby_ip = "10.10.10.2" - stateful_failover_active_ip = "10.10.10.1" - stateful_failover_name = "Stateful-INTERFACE" - stateful_failover_subnet_mask = "255.255.255.0" - stateful_failover_ipv6_addr = false - stateful_failover_interface_name = "GigabitEthernet0/0" - stateful_failover_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470" - stateful_failover_interface_type = "PhysicalInterface" - action = "SWITCH" - force_break = false + ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482" + ha_link_interface_name = "GigabitEthernet0/0" + ha_link_interface_type = "" + ha_link_logical_name = "LAN-INTERFACE" + ha_link_use_ipv6 = false + ha_link_primary_ip = "1.1.1.1" + ha_link_secondary_ip = "1.1.1.2" + ha_link_netmask = "255.255.255.0" + state_link_use_same_as_ha = false + state_link_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470" + state_link_interface_name = "GigabitEthernet0/0" + state_link_interface_type = "PhysicalInterface" + state_link_logical_name = "Stateful-INTERFACE" + state_link_use_ipv6 = false + state_link_primary_ip = "10.10.10.1" + state_link_secondary_ip = "10.10.10.2" + state_link_netmask = "255.255.255.0" + encryption_enabled = true + encryption_key_generation_scheme = "AUTO" + failed_interfaces_limit = 1 + peer_poll_time = 1 + peer_poll_time_unit = "SEC" + peer_hold_time = 15 + peer_hold_time_unit = "SEC" + interface_poll_time = 5 + interface_poll_time_unit = "SEC" + interface_hold_time = 25 } diff --git a/examples/resources/fmc_device_ha_pairs/import.sh b/examples/resources/fmc_device_ha_pairs/import.sh new file mode 100644 index 00000000..ea9a2e67 --- /dev/null +++ b/examples/resources/fmc_device_ha_pairs/import.sh @@ -0,0 +1 @@ +terraform import fmc_device_ha_pairs.example "" diff --git a/examples/resources/fmc_device_ha_pairs/resource.tf b/examples/resources/fmc_device_ha_pairs/resource.tf new file mode 100644 index 00000000..fc4def7f --- /dev/null +++ b/examples/resources/fmc_device_ha_pairs/resource.tf @@ -0,0 +1,27 @@ +resource "fmc_device_ha_pairs" "example" { + name = "FTD_HA" + primary_device_id = "" + secondary_device_id = "" + is_encryption_enabled = false + use_same_link_for_failovers = false + shared_key = "cisco123" + enc_key_generation_scheme = "CUSTOM" + lan_failover_standby_ip = "1.1.1.2" + lan_failover_active_ip = "1.1.1.1" + lan_failover_name = "LAN-INTERFACE" + lan_failover_subnet_mask = "255.255.255.0" + lan_failover_ipv6_addr = false + lan_failover_interface_name = "GigabitEthernet0/0" + lan_failover_interface_id = "" + lan_failover_interface_type = "PhysicalInterface" + stateful_failover_standby_ip = "1.1.1.2" + stateful_failover_active_ip = "1.1.1.1" + stateful_failover_name = "Stateful-INTERFACE" + stateful_failover_subnet_mask = "255.255.255.0" + stateful_failover_ipv6_addr = false + stateful_failover_interface_name = "GigabitEthernet0/0" + stateful_failover_interface_id = "" + stateful_failover_interface_type = "PhysicalInterface" + action = "SWITCH" + force_break = false +} diff --git a/gen/definitions/device_ha_pair.yaml b/gen/definitions/device_ha_pair.yaml new file mode 100644 index 00000000..d46fd4e5 --- /dev/null +++ b/gen/definitions/device_ha_pair.yaml @@ -0,0 +1,277 @@ +--- +name: Device HA Pair +rest_endpoint: /api/fmc_config/v1/domain/{DOMAIN_UUID}/devicehapairs/ftddevicehapairs +doc_category: Device +test_tags: [TF_VAR_device_id, TF_VAR_device_2_id] +res_description: Resource to manage HA Pair. failed_interfaces_limit or failed_interfaces_percent needs to be set. +attributes: + - model_name: name + type: String + mandatory: true + description: The name of the High Availability (HA) Pair. + data_source_query: true + example: FTD_HA + - model_name: type + type: String + description: Type of the resource; This is always `DeviceHAPair`. + computed: true + - model_name: id + type: String + mandatory: true + tf_name: primary_device_id + requires_replace: true + data_path: [primary] + description: ID of primary FTD in the HA Pair. + example: 76d24097-41c4-4558-a4d0-a8c07ac08470 + test_value: var.device_id + - model_name: id + type: String + mandatory: true + tf_name: secondary_device_id + requires_replace: true + data_path: [secondary] + description: ID of secondary FTD in the HA Pair. + example: 96d24097-41c4-4332-a4d0-a8c07ac08482 + test_value: var.device_2_id +# HA Link + - model_name: id + type: String + mandatory: true + tf_name: ha_link_interface_id + data_path: [ftdHABootstrap,lanFailover,interfaceObject] + description: ID of High Availability Link interface. + example: 96d24097-41c4-4332-a4d0-a8c07ac08482 + write_only: true + requires_replace: true + - model_name: name + type: String + tf_name: ha_link_interface_name + data_path: [ftdHABootstrap,lanFailover,interfaceObject] + description: Name of High Availability Link interface. + example: GigabitEthernet0/0 + mandatory: true + requires_replace: true + - model_name: type + type: String + mandatory: true + tf_name: ha_link_interface_type + description: Type of High Availability Link interface. + data_path: [ftdHABootstrap,lanFailover,interfaceObject] + write_only: true + requires_replace: true + - model_name: logicalName + type: String + mandatory: true + tf_name: ha_link_logical_name + data_path: [ftdHABootstrap,lanFailover] + example: LAN-INTERFACE + description: The logical name of failover interface. + requires_replace: true + - model_name: useIPv6Address + type: Bool + tf_name: ha_link_use_ipv6 + default_value: false + data_path: [ftdHABootstrap,lanFailover] + example: false + description: Use IPv6 addressing for HA communication. + requires_replace: true + - model_name: activeIP + tf_name: ha_link_primary_ip + mandatory: true + type: String + data_path: [ftdHABootstrap,lanFailover] + description: The IP of primary node interface. + example: "1.1.1.1" + requires_replace: true + - model_name: standbyIP + type: String + mandatory: true + tf_name: ha_link_secondary_ip + data_path: [ftdHABootstrap,lanFailover] + example: "1.1.1.2" + description: The IP of secondary node interface. + requires_replace: true + - model_name: subnetMask + type: String + tf_name: ha_link_netmask + mandatory: true + data_path: [ftdHABootstrap,lanFailover] + description: Subnet mask for HA link. + example: "255.255.255.0" + requires_replace: true +# State Link + - model_name: useSameLinkForFailovers + tf_name: state_link_use_same_as_ha + type: Bool + mandatory: true + data_path: [ftdHABootstrap] + example: false + description: Use the same link for state and HA. + write_only: true + requires_replace: true + - model_name: id + type: String + tf_name: state_link_interface_id + data_path: [ftdHABootstrap,statefulFailover,interfaceObject] + description: ID of physical interface. + example: 76d24097-hj7r-7786-a4d0-a8c07ac08470 + write_only: true + requires_replace: true + - model_name: name + type: String + tf_name: state_link_interface_name + data_path: [ftdHABootstrap,statefulFailover,interfaceObject] + description: Name of state link interface. + example: GigabitEthernet0/0 + requires_replace: true + - model_name: type + type: String + tf_name: state_link_interface_type + data_path: [ftdHABootstrap,statefulFailover,interfaceObject] + description: Type of state link interface. + example: PhysicalInterface + write_only: true + requires_replace: true + - model_name: logicalName + type: String + tf_name: state_link_logical_name + data_path: [ftdHABootstrap,statefulFailover] + example: Stateful-INTERFACE + requires_replace: true + - model_name: useIPv6Address + type: Bool + tf_name: state_link_use_ipv6 + data_path: [ftdHABootstrap,statefulFailover] + example: false + description: Use IPv6 addressing for HA communication. + requires_replace: true + - model_name: activeIP + tf_name: state_link_primary_ip + type: String + data_path: [ftdHABootstrap,statefulFailover] + example: "10.10.10.1" + description: The IP of primary node interface. + requires_replace: true + - model_name: standbyIP + type: String + tf_name: state_link_secondary_ip + data_path: [ftdHABootstrap,statefulFailover] + example: "10.10.10.2" + description: The IP of secondary node interface. + requires_replace: true + - model_name: subnetMask + type: String + tf_name: state_link_netmask + data_path: [ftdHABootstrap,statefulFailover] + example: "255.255.255.0" + description: Subnet mask for state link. + requires_replace: true +# IPsec Encryption + - model_name: isEncryptionEnabled + tf_name: encryption_enabled + type: Bool + data_path: [ftdHABootstrap] + description: Use encryption for communication. + example: "true" + requires_replace: true + - model_name: encKeyGenerationScheme + tf_name: encryption_key_generation_scheme + type: String + data_path: [ftdHABootstrap] + description: Select the encyption key generation scheme. + example: "AUTO" + enum_values: [ AUTO, CUSTOM ] + write_only: true + requires_replace: true + - model_name: sharedKey + type: String + data_path: [ftdHABootstrap] + tf_name: encryption_key + description: Pass shared key for encryption if CUSTOM key geneeration scheme is selected. + exclude_example: true + exclude_test: true + write_only: true + requires_replace: true +# Failover trigger criteria - Interface Failure Treshold ### Changes in this section will require changes in `go` code + - model_name: percentFailedInterfaceExceed + data_path: [ftdHAFailoverTriggerCriteria] + tf_name: failed_interfaces_percent + type: Int64 + description: Percentage of Failed Interfaces that triggers failover. + min_int: 1 + max_int: 100 + exclude_test: true + exclude_example: true + - model_name: noOfFailedInterfaceLimit + data_path: [ftdHAFailoverTriggerCriteria] + tf_name: failed_interfaces_limit + description: Number of Failed Interfaces that triggers failover. + type: Int64 + min_int: 1 + max_int: 211 + example: 1 +# Failover trigger criteria - Hello Packet Intervals ### Changes in this section will require changes in `go` code + - model_name: peerPollTime + data_path: [ftdHAFailoverTriggerCriteria] + type: Int64 + description: Peer Pool Time (1-15 SEC or 200-999 MSEC) + min_int: 1 + max_int: 999 + default_value: 1 + example: 1 + - model_name: peerPollTimeUnit + data_path: [ftdHAFailoverTriggerCriteria] + type: String + description: Peer Pool Time Unit + enum_values: [ SEC, MSEC ] + default_value: SEC + example: SEC + - model_name: peerHoldTime + data_path: [ftdHAFailoverTriggerCriteria] + type: Int64 + description: Peer Hold Time (3-45 SEC or 800-999 MSEC) + min_int: 3 + max_int: 999 + default_value: 15 + example: 15 + - model_name: peerHoldTimeUnit + data_path: [ftdHAFailoverTriggerCriteria] + type: String + description: Peer Hold Time Unit + enum_values: [ SEC, MSEC ] + default_value: SEC + example: SEC + - model_name: interfacePollTime + data_path: [ftdHAFailoverTriggerCriteria] + type: Int64 + description: Peer Pool Time (1-15 SEC or 500-999 MSEC) + min_int: 1 + max_int: 999 + default_value: 5 + example: 5 + - model_name: interfacePollTimeUnit + data_path: [ftdHAFailoverTriggerCriteria] + type: String + description: Peer Pool Time Unit + enum_values: [ SEC, MSEC ] + default_value: SEC + example: SEC + - model_name: interfaceHoldTime + data_path: [ftdHAFailoverTriggerCriteria] + type: Int64 + description: Interface Hold Time in seconds + min_int: 25 + max_int: 75 + default_value: 25 + example: 25 +# Other + - model_name: action + type: String + description: FTD HA PUT operation action. Specifically used for manual switch. HA Break will be triggered when you run terraform destroy + enum_values: [ SWITCH, HABREAK ] + exclude_test: true + exclude_example: true + +test_prerequisites: |- + variable "device_id" { default = null } // tests will set $TF_VAR_device_id + variable "device_2_id" { default = null } // tests will set $TF_VAR_device_2_id \ No newline at end of file diff --git a/internal/provider/data_source_fmc_device_ha_pair.go b/internal/provider/data_source_fmc_device_ha_pair.go new file mode 100644 index 00000000..0219c134 --- /dev/null +++ b/internal/provider/data_source_fmc_device_ha_pair.go @@ -0,0 +1,297 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public 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 +// +// https://mozilla.org/MPL/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. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-fmc" + "github.com/tidwall/gjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &DeviceHAPairDataSource{} + _ datasource.DataSourceWithConfigure = &DeviceHAPairDataSource{} +) + +func NewDeviceHAPairDataSource() datasource.DataSource { + return &DeviceHAPairDataSource{} +} + +type DeviceHAPairDataSource struct { + client *fmc.Client +} + +func (d *DeviceHAPairDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_ha_pair" +} + +func (d *DeviceHAPairDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This data source can read the Device HA Pair.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Optional: true, + Computed: true, + }, + "domain": schema.StringAttribute{ + MarkdownDescription: "The name of the FMC domain", + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "The name of the High Availability (HA) Pair.", + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "Type of the resource; This is always `DeviceHAPair`.", + Computed: true, + }, + "primary_device_id": schema.StringAttribute{ + MarkdownDescription: "ID of primary FTD in the HA Pair.", + Computed: true, + }, + "secondary_device_id": schema.StringAttribute{ + MarkdownDescription: "ID of secondary FTD in the HA Pair.", + Computed: true, + }, + "ha_link_interface_id": schema.StringAttribute{ + MarkdownDescription: "ID of High Availability Link interface.", + Computed: true, + }, + "ha_link_interface_name": schema.StringAttribute{ + MarkdownDescription: "Name of High Availability Link interface.", + Computed: true, + }, + "ha_link_interface_type": schema.StringAttribute{ + MarkdownDescription: "Type of High Availability Link interface.", + Computed: true, + }, + "ha_link_logical_name": schema.StringAttribute{ + MarkdownDescription: "The logical name of failover interface.", + Computed: true, + }, + "ha_link_use_ipv6": schema.BoolAttribute{ + MarkdownDescription: "Use IPv6 addressing for HA communication.", + Computed: true, + }, + "ha_link_primary_ip": schema.StringAttribute{ + MarkdownDescription: "The IP of primary node interface.", + Computed: true, + }, + "ha_link_secondary_ip": schema.StringAttribute{ + MarkdownDescription: "The IP of secondary node interface.", + Computed: true, + }, + "ha_link_netmask": schema.StringAttribute{ + MarkdownDescription: "Subnet mask for HA link.", + Computed: true, + }, + "state_link_use_same_as_ha": schema.BoolAttribute{ + MarkdownDescription: "Use the same link for state and HA.", + Computed: true, + }, + "state_link_interface_id": schema.StringAttribute{ + MarkdownDescription: "ID of physical interface.", + Computed: true, + }, + "state_link_interface_name": schema.StringAttribute{ + MarkdownDescription: "Name of state link interface.", + Computed: true, + }, + "state_link_interface_type": schema.StringAttribute{ + MarkdownDescription: "Type of state link interface.", + Computed: true, + }, + "state_link_logical_name": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "state_link_use_ipv6": schema.BoolAttribute{ + MarkdownDescription: "Use IPv6 addressing for HA communication.", + Computed: true, + }, + "state_link_primary_ip": schema.StringAttribute{ + MarkdownDescription: "The IP of primary node interface.", + Computed: true, + }, + "state_link_secondary_ip": schema.StringAttribute{ + MarkdownDescription: "The IP of secondary node interface.", + Computed: true, + }, + "state_link_netmask": schema.StringAttribute{ + MarkdownDescription: "Subnet mask for state link.", + Computed: true, + }, + "encryption_enabled": schema.BoolAttribute{ + MarkdownDescription: "Use encryption for communication.", + Computed: true, + }, + "encryption_key_generation_scheme": schema.StringAttribute{ + MarkdownDescription: "Select the encyption key generation scheme.", + Computed: true, + }, + "encryption_key": schema.StringAttribute{ + MarkdownDescription: "Pass shared key for encryption if CUSTOM key geneeration scheme is selected.", + Computed: true, + }, + "failed_interfaces_percent": schema.Int64Attribute{ + MarkdownDescription: "Percentage of Failed Interfaces that triggers failover.", + Computed: true, + }, + "failed_interfaces_limit": schema.Int64Attribute{ + MarkdownDescription: "Number of Failed Interfaces that triggers failover.", + Computed: true, + }, + "peer_poll_time": schema.Int64Attribute{ + MarkdownDescription: "Peer Pool Time (1-15 SEC or 200-999 MSEC)", + Computed: true, + }, + "peer_poll_time_unit": schema.StringAttribute{ + MarkdownDescription: "Peer Pool Time Unit", + Computed: true, + }, + "peer_hold_time": schema.Int64Attribute{ + MarkdownDescription: "Peer Hold Time (3-45 SEC or 800-999 MSEC)", + Computed: true, + }, + "peer_hold_time_unit": schema.StringAttribute{ + MarkdownDescription: "Peer Hold Time Unit", + Computed: true, + }, + "interface_poll_time": schema.Int64Attribute{ + MarkdownDescription: "Peer Pool Time (1-15 SEC or 500-999 MSEC)", + Computed: true, + }, + "interface_poll_time_unit": schema.StringAttribute{ + MarkdownDescription: "Peer Pool Time Unit", + Computed: true, + }, + "interface_hold_time": schema.Int64Attribute{ + MarkdownDescription: "Interface Hold Time in seconds", + Computed: true, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "FTD HA PUT operation action. Specifically used for manual switch. HA Break will be triggered when you run terraform destroy", + Computed: true, + }, + }, + } +} +func (d *DeviceHAPairDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.ExactlyOneOf( + path.MatchRoot("id"), + path.MatchRoot("name"), + ), + } +} + +func (d *DeviceHAPairDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*FmcProviderData).Client +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (d *DeviceHAPairDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config DeviceHAPair + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !config.Domain.IsNull() && config.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(config.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.Id.String())) + if config.Id.IsNull() && !config.Name.IsNull() { + offset := 0 + limit := 1000 + for page := 1; ; page++ { + queryString := fmt.Sprintf("?limit=%d&offset=%d&expanded=true", limit, offset) + res, err := d.client.Get(config.getPath()+queryString, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve objects, got error: %s", err)) + return + } + if value := res.Get("items"); len(value.Array()) > 0 { + value.ForEach(func(k, v gjson.Result) bool { + if config.Name.ValueString() == v.Get("name").String() { + config.Id = types.StringValue(v.Get("id").String()) + tflog.Debug(ctx, fmt.Sprintf("%s: Found object with name '%v', id: %v", config.Id.String(), config.Name.ValueString(), config.Id.String())) + return false + } + return true + }) + } + if !config.Id.IsNull() || !res.Get("paging.next.0").Exists() { + break + } + offset += limit + } + + if config.Id.IsNull() { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to find object with name: %s", config.Name.ValueString())) + return + } + } + urlPath := config.getPath() + "/" + url.QueryEscape(config.Id.ValueString()) + res, err := d.client.Get(urlPath, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + config.fromBody(ctx, res) + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.Id.ValueString())) + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end read diff --git a/internal/provider/data_source_fmc_device_ha_pair_test.go b/internal/provider/data_source_fmc_device_ha_pair_test.go new file mode 100644 index 00000000..6738ada0 --- /dev/null +++ b/internal/provider/data_source_fmc_device_ha_pair_test.go @@ -0,0 +1,174 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public 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 +// +// https://mozilla.org/MPL/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. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource + +func TestAccDataSourceFmcDeviceHAPair(t *testing.T) { + if os.Getenv("TF_VAR_device_id") == "" || os.Getenv("TF_VAR_device_2_id") == "" { + t.Skip("skipping test, set environment variable TF_VAR_device_id and TF_VAR_device_2_id") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "name", "FTD_HA")) + checks = append(checks, resource.TestCheckResourceAttrSet("data.fmc_device_ha_pair.test", "type")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_interface_name", "GigabitEthernet0/0")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_logical_name", "LAN-INTERFACE")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_use_ipv6", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_primary_ip", "1.1.1.1")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_secondary_ip", "1.1.1.2")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "ha_link_netmask", "255.255.255.0")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_interface_name", "GigabitEthernet0/0")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_logical_name", "Stateful-INTERFACE")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_use_ipv6", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_primary_ip", "10.10.10.1")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_secondary_ip", "10.10.10.2")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "state_link_netmask", "255.255.255.0")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "encryption_enabled", "true")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "failed_interfaces_limit", "1")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "peer_poll_time", "1")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "peer_poll_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "peer_hold_time", "15")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "peer_hold_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "interface_poll_time", "5")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "interface_poll_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("data.fmc_device_ha_pair.test", "interface_hold_time", "25")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ErrorCheck: func(err error) error { return testAccErrorCheck(t, err) }, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceFmcDeviceHAPairPrerequisitesConfig + testAccDataSourceFmcDeviceHAPairConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + Config: testAccDataSourceFmcDeviceHAPairPrerequisitesConfig + testAccNamedDataSourceFmcDeviceHAPairConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccDataSourceFmcDeviceHAPairPrerequisitesConfig = ` +variable "device_id" { default = null } // tests will set $TF_VAR_device_id +variable "device_2_id" { default = null } // tests will set $TF_VAR_device_2_id +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceFmcDeviceHAPairConfig() string { + config := `resource "fmc_device_ha_pair" "test" {` + "\n" + config += ` name = "FTD_HA"` + "\n" + config += ` primary_device_id = var.device_id` + "\n" + config += ` secondary_device_id = var.device_2_id` + "\n" + config += ` ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482"` + "\n" + config += ` ha_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` ha_link_interface_type = ""` + "\n" + config += ` ha_link_logical_name = "LAN-INTERFACE"` + "\n" + config += ` ha_link_use_ipv6 = false` + "\n" + config += ` ha_link_primary_ip = "1.1.1.1"` + "\n" + config += ` ha_link_secondary_ip = "1.1.1.2"` + "\n" + config += ` ha_link_netmask = "255.255.255.0"` + "\n" + config += ` state_link_use_same_as_ha = false` + "\n" + config += ` state_link_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470"` + "\n" + config += ` state_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` state_link_interface_type = "PhysicalInterface"` + "\n" + config += ` state_link_logical_name = "Stateful-INTERFACE"` + "\n" + config += ` state_link_use_ipv6 = false` + "\n" + config += ` state_link_primary_ip = "10.10.10.1"` + "\n" + config += ` state_link_secondary_ip = "10.10.10.2"` + "\n" + config += ` state_link_netmask = "255.255.255.0"` + "\n" + config += ` encryption_enabled = true` + "\n" + config += ` encryption_key_generation_scheme = "AUTO"` + "\n" + config += ` failed_interfaces_limit = 1` + "\n" + config += ` peer_poll_time = 1` + "\n" + config += ` peer_poll_time_unit = "SEC"` + "\n" + config += ` peer_hold_time = 15` + "\n" + config += ` peer_hold_time_unit = "SEC"` + "\n" + config += ` interface_poll_time = 5` + "\n" + config += ` interface_poll_time_unit = "SEC"` + "\n" + config += ` interface_hold_time = 25` + "\n" + config += `}` + "\n" + + config += ` + data "fmc_device_ha_pair" "test" { + id = fmc_device_ha_pair.test.id + } + ` + return config +} + +func testAccNamedDataSourceFmcDeviceHAPairConfig() string { + config := `resource "fmc_device_ha_pair" "test" {` + "\n" + config += ` name = "FTD_HA"` + "\n" + config += ` primary_device_id = var.device_id` + "\n" + config += ` secondary_device_id = var.device_2_id` + "\n" + config += ` ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482"` + "\n" + config += ` ha_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` ha_link_interface_type = ""` + "\n" + config += ` ha_link_logical_name = "LAN-INTERFACE"` + "\n" + config += ` ha_link_use_ipv6 = false` + "\n" + config += ` ha_link_primary_ip = "1.1.1.1"` + "\n" + config += ` ha_link_secondary_ip = "1.1.1.2"` + "\n" + config += ` ha_link_netmask = "255.255.255.0"` + "\n" + config += ` state_link_use_same_as_ha = false` + "\n" + config += ` state_link_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470"` + "\n" + config += ` state_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` state_link_interface_type = "PhysicalInterface"` + "\n" + config += ` state_link_logical_name = "Stateful-INTERFACE"` + "\n" + config += ` state_link_use_ipv6 = false` + "\n" + config += ` state_link_primary_ip = "10.10.10.1"` + "\n" + config += ` state_link_secondary_ip = "10.10.10.2"` + "\n" + config += ` state_link_netmask = "255.255.255.0"` + "\n" + config += ` encryption_enabled = true` + "\n" + config += ` encryption_key_generation_scheme = "AUTO"` + "\n" + config += ` failed_interfaces_limit = 1` + "\n" + config += ` peer_poll_time = 1` + "\n" + config += ` peer_poll_time_unit = "SEC"` + "\n" + config += ` peer_hold_time = 15` + "\n" + config += ` peer_hold_time_unit = "SEC"` + "\n" + config += ` interface_poll_time = 5` + "\n" + config += ` interface_poll_time_unit = "SEC"` + "\n" + config += ` interface_hold_time = 25` + "\n" + config += `}` + "\n" + + config += ` + data "fmc_device_ha_pair" "test" { + name = fmc_device_ha_pair.test.name + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_fmc_device_ha_pair.go b/internal/provider/model_fmc_device_ha_pair.go new file mode 100644 index 00000000..79ed7f4a --- /dev/null +++ b/internal/provider/model_fmc_device_ha_pair.go @@ -0,0 +1,551 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public 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 +// +// https://mozilla.org/MPL/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. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types + +type DeviceHAPair struct { + Id types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + PrimaryDeviceId types.String `tfsdk:"primary_device_id"` + SecondaryDeviceId types.String `tfsdk:"secondary_device_id"` + HaLinkInterfaceId types.String `tfsdk:"ha_link_interface_id"` + HaLinkInterfaceName types.String `tfsdk:"ha_link_interface_name"` + HaLinkInterfaceType types.String `tfsdk:"ha_link_interface_type"` + HaLinkLogicalName types.String `tfsdk:"ha_link_logical_name"` + HaLinkUseIpv6 types.Bool `tfsdk:"ha_link_use_ipv6"` + HaLinkPrimaryIp types.String `tfsdk:"ha_link_primary_ip"` + HaLinkSecondaryIp types.String `tfsdk:"ha_link_secondary_ip"` + HaLinkNetmask types.String `tfsdk:"ha_link_netmask"` + StateLinkUseSameAsHa types.Bool `tfsdk:"state_link_use_same_as_ha"` + StateLinkInterfaceId types.String `tfsdk:"state_link_interface_id"` + StateLinkInterfaceName types.String `tfsdk:"state_link_interface_name"` + StateLinkInterfaceType types.String `tfsdk:"state_link_interface_type"` + StateLinkLogicalName types.String `tfsdk:"state_link_logical_name"` + StateLinkUseIpv6 types.Bool `tfsdk:"state_link_use_ipv6"` + StateLinkPrimaryIp types.String `tfsdk:"state_link_primary_ip"` + StateLinkSecondaryIp types.String `tfsdk:"state_link_secondary_ip"` + StateLinkNetmask types.String `tfsdk:"state_link_netmask"` + EncryptionEnabled types.Bool `tfsdk:"encryption_enabled"` + EncryptionKeyGenerationScheme types.String `tfsdk:"encryption_key_generation_scheme"` + EncryptionKey types.String `tfsdk:"encryption_key"` + FailedInterfacesPercent types.Int64 `tfsdk:"failed_interfaces_percent"` + FailedInterfacesLimit types.Int64 `tfsdk:"failed_interfaces_limit"` + PeerPollTime types.Int64 `tfsdk:"peer_poll_time"` + PeerPollTimeUnit types.String `tfsdk:"peer_poll_time_unit"` + PeerHoldTime types.Int64 `tfsdk:"peer_hold_time"` + PeerHoldTimeUnit types.String `tfsdk:"peer_hold_time_unit"` + InterfacePollTime types.Int64 `tfsdk:"interface_poll_time"` + InterfacePollTimeUnit types.String `tfsdk:"interface_poll_time_unit"` + InterfaceHoldTime types.Int64 `tfsdk:"interface_hold_time"` + Action types.String `tfsdk:"action"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data DeviceHAPair) getPath() string { + return "/api/fmc_config/v1/domain/{DOMAIN_UUID}/devicehapairs/ftddevicehapairs" +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data DeviceHAPair) toBody(ctx context.Context, state DeviceHAPair) string { + body := "" + if data.Id.ValueString() != "" { + body, _ = sjson.Set(body, "id", data.Id.ValueString()) + } + if !data.Name.IsNull() { + body, _ = sjson.Set(body, "name", data.Name.ValueString()) + } + if !data.PrimaryDeviceId.IsNull() { + body, _ = sjson.Set(body, "primary.id", data.PrimaryDeviceId.ValueString()) + } + if !data.SecondaryDeviceId.IsNull() { + body, _ = sjson.Set(body, "secondary.id", data.SecondaryDeviceId.ValueString()) + } + if !data.HaLinkInterfaceId.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.interfaceObject.id", data.HaLinkInterfaceId.ValueString()) + } + if !data.HaLinkInterfaceName.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.interfaceObject.name", data.HaLinkInterfaceName.ValueString()) + } + if !data.HaLinkInterfaceType.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.interfaceObject.type", data.HaLinkInterfaceType.ValueString()) + } + if !data.HaLinkLogicalName.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.logicalName", data.HaLinkLogicalName.ValueString()) + } + if !data.HaLinkUseIpv6.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.useIPv6Address", data.HaLinkUseIpv6.ValueBool()) + } + if !data.HaLinkPrimaryIp.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.activeIP", data.HaLinkPrimaryIp.ValueString()) + } + if !data.HaLinkSecondaryIp.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.standbyIP", data.HaLinkSecondaryIp.ValueString()) + } + if !data.HaLinkNetmask.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.lanFailover.subnetMask", data.HaLinkNetmask.ValueString()) + } + if !data.StateLinkUseSameAsHa.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.useSameLinkForFailovers", data.StateLinkUseSameAsHa.ValueBool()) + } + if !data.StateLinkInterfaceId.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.interfaceObject.id", data.StateLinkInterfaceId.ValueString()) + } + if !data.StateLinkInterfaceName.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.interfaceObject.name", data.StateLinkInterfaceName.ValueString()) + } + if !data.StateLinkInterfaceType.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.interfaceObject.type", data.StateLinkInterfaceType.ValueString()) + } + if !data.StateLinkLogicalName.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.logicalName", data.StateLinkLogicalName.ValueString()) + } + if !data.StateLinkUseIpv6.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.useIPv6Address", data.StateLinkUseIpv6.ValueBool()) + } + if !data.StateLinkPrimaryIp.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.activeIP", data.StateLinkPrimaryIp.ValueString()) + } + if !data.StateLinkSecondaryIp.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.standbyIP", data.StateLinkSecondaryIp.ValueString()) + } + if !data.StateLinkNetmask.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.statefulFailover.subnetMask", data.StateLinkNetmask.ValueString()) + } + if !data.EncryptionEnabled.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.isEncryptionEnabled", data.EncryptionEnabled.ValueBool()) + } + if !data.EncryptionKeyGenerationScheme.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.encKeyGenerationScheme", data.EncryptionKeyGenerationScheme.ValueString()) + } + if !data.EncryptionKey.IsNull() { + body, _ = sjson.Set(body, "ftdHABootstrap.sharedKey", data.EncryptionKey.ValueString()) + } + if !data.FailedInterfacesPercent.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.percentFailedInterfaceExceed", data.FailedInterfacesPercent.ValueInt64()) + } + if !data.FailedInterfacesLimit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.noOfFailedInterfaceLimit", data.FailedInterfacesLimit.ValueInt64()) + } + if !data.PeerPollTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerPollTime", data.PeerPollTime.ValueInt64()) + } + if !data.PeerPollTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerPollTimeUnit", data.PeerPollTimeUnit.ValueString()) + } + if !data.PeerHoldTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerHoldTime", data.PeerHoldTime.ValueInt64()) + } + if !data.PeerHoldTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerHoldTimeUnit", data.PeerHoldTimeUnit.ValueString()) + } + if !data.InterfacePollTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfacePollTime", data.InterfacePollTime.ValueInt64()) + } + if !data.InterfacePollTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfacePollTimeUnit", data.InterfacePollTimeUnit.ValueString()) + } + if !data.InterfaceHoldTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfaceHoldTime", data.InterfaceHoldTime.ValueInt64()) + } + if !data.Action.IsNull() { + body, _ = sjson.Set(body, "action", data.Action.ValueString()) + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *DeviceHAPair) fromBody(ctx context.Context, res gjson.Result) { + if value := res.Get("name"); value.Exists() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("type"); value.Exists() { + data.Type = types.StringValue(value.String()) + } else { + data.Type = types.StringNull() + } + if value := res.Get("primary.id"); value.Exists() { + data.PrimaryDeviceId = types.StringValue(value.String()) + } else { + data.PrimaryDeviceId = types.StringNull() + } + if value := res.Get("secondary.id"); value.Exists() { + data.SecondaryDeviceId = types.StringValue(value.String()) + } else { + data.SecondaryDeviceId = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.interfaceObject.name"); value.Exists() { + data.HaLinkInterfaceName = types.StringValue(value.String()) + } else { + data.HaLinkInterfaceName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.logicalName"); value.Exists() { + data.HaLinkLogicalName = types.StringValue(value.String()) + } else { + data.HaLinkLogicalName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.useIPv6Address"); value.Exists() { + data.HaLinkUseIpv6 = types.BoolValue(value.Bool()) + } else { + data.HaLinkUseIpv6 = types.BoolValue(false) + } + if value := res.Get("ftdHABootstrap.lanFailover.activeIP"); value.Exists() { + data.HaLinkPrimaryIp = types.StringValue(value.String()) + } else { + data.HaLinkPrimaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.standbyIP"); value.Exists() { + data.HaLinkSecondaryIp = types.StringValue(value.String()) + } else { + data.HaLinkSecondaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.subnetMask"); value.Exists() { + data.HaLinkNetmask = types.StringValue(value.String()) + } else { + data.HaLinkNetmask = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.interfaceObject.name"); value.Exists() { + data.StateLinkInterfaceName = types.StringValue(value.String()) + } else { + data.StateLinkInterfaceName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.logicalName"); value.Exists() { + data.StateLinkLogicalName = types.StringValue(value.String()) + } else { + data.StateLinkLogicalName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.useIPv6Address"); value.Exists() { + data.StateLinkUseIpv6 = types.BoolValue(value.Bool()) + } else { + data.StateLinkUseIpv6 = types.BoolNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.activeIP"); value.Exists() { + data.StateLinkPrimaryIp = types.StringValue(value.String()) + } else { + data.StateLinkPrimaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.standbyIP"); value.Exists() { + data.StateLinkSecondaryIp = types.StringValue(value.String()) + } else { + data.StateLinkSecondaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.subnetMask"); value.Exists() { + data.StateLinkNetmask = types.StringValue(value.String()) + } else { + data.StateLinkNetmask = types.StringNull() + } + if value := res.Get("ftdHABootstrap.isEncryptionEnabled"); value.Exists() { + data.EncryptionEnabled = types.BoolValue(value.Bool()) + } else { + data.EncryptionEnabled = types.BoolNull() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.percentFailedInterfaceExceed"); value.Exists() { + data.FailedInterfacesPercent = types.Int64Value(value.Int()) + } else { + data.FailedInterfacesPercent = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.noOfFailedInterfaceLimit"); value.Exists() { + data.FailedInterfacesLimit = types.Int64Value(value.Int()) + } else { + data.FailedInterfacesLimit = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerPollTime"); value.Exists() { + data.PeerPollTime = types.Int64Value(value.Int()) + } else { + data.PeerPollTime = types.Int64Value(1) + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerPollTimeUnit"); value.Exists() { + data.PeerPollTimeUnit = types.StringValue(value.String()) + } else { + data.PeerPollTimeUnit = types.StringValue("SEC") + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerHoldTime"); value.Exists() { + data.PeerHoldTime = types.Int64Value(value.Int()) + } else { + data.PeerHoldTime = types.Int64Value(15) + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerHoldTimeUnit"); value.Exists() { + data.PeerHoldTimeUnit = types.StringValue(value.String()) + } else { + data.PeerHoldTimeUnit = types.StringValue("SEC") + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfacePollTime"); value.Exists() { + data.InterfacePollTime = types.Int64Value(value.Int()) + } else { + data.InterfacePollTime = types.Int64Value(5) + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfacePollTimeUnit"); value.Exists() { + data.InterfacePollTimeUnit = types.StringValue(value.String()) + } else { + data.InterfacePollTimeUnit = types.StringValue("SEC") + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfaceHoldTime"); value.Exists() { + data.InterfaceHoldTime = types.Int64Value(value.Int()) + } else { + data.InterfaceHoldTime = types.Int64Value(25) + } + if value := res.Get("action"); value.Exists() { + data.Action = types.StringValue(value.String()) + } else { + data.Action = types.StringNull() + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyPartial + +// fromBodyPartial reads values from a gjson.Result into a tfstate model. It ignores null attributes in order to +// uncouple the provider from the exact values that the backend API might summon to replace nulls. (Such behavior might +// easily change across versions of the backend API.) For List/Set/Map attributes, the func only updates the +// "managed" elements, instead of all elements. +func (data *DeviceHAPair) fromBodyPartial(ctx context.Context, res gjson.Result) { + if value := res.Get("name"); value.Exists() && !data.Name.IsNull() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get("type"); value.Exists() && !data.Type.IsNull() { + data.Type = types.StringValue(value.String()) + } else { + data.Type = types.StringNull() + } + if value := res.Get("primary.id"); value.Exists() && !data.PrimaryDeviceId.IsNull() { + data.PrimaryDeviceId = types.StringValue(value.String()) + } else { + data.PrimaryDeviceId = types.StringNull() + } + if value := res.Get("secondary.id"); value.Exists() && !data.SecondaryDeviceId.IsNull() { + data.SecondaryDeviceId = types.StringValue(value.String()) + } else { + data.SecondaryDeviceId = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.interfaceObject.name"); value.Exists() && !data.HaLinkInterfaceName.IsNull() { + data.HaLinkInterfaceName = types.StringValue(value.String()) + } else { + data.HaLinkInterfaceName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.logicalName"); value.Exists() && !data.HaLinkLogicalName.IsNull() { + data.HaLinkLogicalName = types.StringValue(value.String()) + } else { + data.HaLinkLogicalName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.useIPv6Address"); value.Exists() && !data.HaLinkUseIpv6.IsNull() { + data.HaLinkUseIpv6 = types.BoolValue(value.Bool()) + } else if data.HaLinkUseIpv6.ValueBool() != false { + data.HaLinkUseIpv6 = types.BoolNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.activeIP"); value.Exists() && !data.HaLinkPrimaryIp.IsNull() { + data.HaLinkPrimaryIp = types.StringValue(value.String()) + } else { + data.HaLinkPrimaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.standbyIP"); value.Exists() && !data.HaLinkSecondaryIp.IsNull() { + data.HaLinkSecondaryIp = types.StringValue(value.String()) + } else { + data.HaLinkSecondaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.lanFailover.subnetMask"); value.Exists() && !data.HaLinkNetmask.IsNull() { + data.HaLinkNetmask = types.StringValue(value.String()) + } else { + data.HaLinkNetmask = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.interfaceObject.name"); value.Exists() && !data.StateLinkInterfaceName.IsNull() { + data.StateLinkInterfaceName = types.StringValue(value.String()) + } else { + data.StateLinkInterfaceName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.logicalName"); value.Exists() && !data.StateLinkLogicalName.IsNull() { + data.StateLinkLogicalName = types.StringValue(value.String()) + } else { + data.StateLinkLogicalName = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.useIPv6Address"); value.Exists() && !data.StateLinkUseIpv6.IsNull() { + data.StateLinkUseIpv6 = types.BoolValue(value.Bool()) + } else { + data.StateLinkUseIpv6 = types.BoolNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.activeIP"); value.Exists() && !data.StateLinkPrimaryIp.IsNull() { + data.StateLinkPrimaryIp = types.StringValue(value.String()) + } else { + data.StateLinkPrimaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.standbyIP"); value.Exists() && !data.StateLinkSecondaryIp.IsNull() { + data.StateLinkSecondaryIp = types.StringValue(value.String()) + } else { + data.StateLinkSecondaryIp = types.StringNull() + } + if value := res.Get("ftdHABootstrap.statefulFailover.subnetMask"); value.Exists() && !data.StateLinkNetmask.IsNull() { + data.StateLinkNetmask = types.StringValue(value.String()) + } else { + data.StateLinkNetmask = types.StringNull() + } + if value := res.Get("ftdHABootstrap.isEncryptionEnabled"); value.Exists() && !data.EncryptionEnabled.IsNull() { + data.EncryptionEnabled = types.BoolValue(value.Bool()) + } else { + data.EncryptionEnabled = types.BoolNull() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.percentFailedInterfaceExceed"); value.Exists() && !data.FailedInterfacesPercent.IsNull() { + data.FailedInterfacesPercent = types.Int64Value(value.Int()) + } else { + data.FailedInterfacesPercent = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.noOfFailedInterfaceLimit"); value.Exists() && !data.FailedInterfacesLimit.IsNull() { + data.FailedInterfacesLimit = types.Int64Value(value.Int()) + } else { + data.FailedInterfacesLimit = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerPollTime"); value.Exists() && !data.PeerPollTime.IsNull() { + data.PeerPollTime = types.Int64Value(value.Int()) + } else if data.PeerPollTime.ValueInt64() != 1 { + data.PeerPollTime = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerPollTimeUnit"); value.Exists() && !data.PeerPollTimeUnit.IsNull() { + data.PeerPollTimeUnit = types.StringValue(value.String()) + } else if data.PeerPollTimeUnit.ValueString() != "SEC" { + data.PeerPollTimeUnit = types.StringNull() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerHoldTime"); value.Exists() && !data.PeerHoldTime.IsNull() { + data.PeerHoldTime = types.Int64Value(value.Int()) + } else if data.PeerHoldTime.ValueInt64() != 15 { + data.PeerHoldTime = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.peerHoldTimeUnit"); value.Exists() && !data.PeerHoldTimeUnit.IsNull() { + data.PeerHoldTimeUnit = types.StringValue(value.String()) + } else if data.PeerHoldTimeUnit.ValueString() != "SEC" { + data.PeerHoldTimeUnit = types.StringNull() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfacePollTime"); value.Exists() && !data.InterfacePollTime.IsNull() { + data.InterfacePollTime = types.Int64Value(value.Int()) + } else if data.InterfacePollTime.ValueInt64() != 5 { + data.InterfacePollTime = types.Int64Null() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfacePollTimeUnit"); value.Exists() && !data.InterfacePollTimeUnit.IsNull() { + data.InterfacePollTimeUnit = types.StringValue(value.String()) + } else if data.InterfacePollTimeUnit.ValueString() != "SEC" { + data.InterfacePollTimeUnit = types.StringNull() + } + if value := res.Get("ftdHAFailoverTriggerCriteria.interfaceHoldTime"); value.Exists() && !data.InterfaceHoldTime.IsNull() { + data.InterfaceHoldTime = types.Int64Value(value.Int()) + } else if data.InterfaceHoldTime.ValueInt64() != 25 { + data.InterfaceHoldTime = types.Int64Null() + } + if value := res.Get("action"); value.Exists() && !data.Action.IsNull() { + data.Action = types.StringValue(value.String()) + } else { + data.Action = types.StringNull() + } +} + +// End of section. //template:end fromBodyPartial + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyUnknowns + +// fromBodyUnknowns updates the Unknown Computed tfstate values from a JSON. +// Known values are not changed (usual for Computed attributes with UseStateForUnknown or with Default). +func (data *DeviceHAPair) fromBodyUnknowns(ctx context.Context, res gjson.Result) { + if data.Type.IsUnknown() { + if value := res.Get("type"); value.Exists() { + data.Type = types.StringValue(value.String()) + } else { + data.Type = types.StringNull() + } + } +} + +// End of section. //template:end fromBodyUnknowns + +// Section below is generated&owned by "gen/generator.go". //template:begin Clone + +// End of section. //template:end Clone + +// Section below is generated&owned by "gen/generator.go". //template:begin toBodyNonBulk + +// End of section. //template:end toBodyNonBulk + +// toBodyPutDelete generates minimal required body to brek the HA. +func (data DeviceHAPair) toBodyPutDelete(ctx context.Context, state DeviceHAPair) string { + body := "" + if data.Id.ValueString() != "" { + body, _ = sjson.Set(body, "id", data.Id.ValueString()) + } + body, _ = sjson.Set(body, "action", "HABREAK") + body, _ = sjson.Set(body, "forceBreak", true) + return body +} + +// toBodyUpdateTimers generates minimal required body to update timers. +// Those settings, even if set, are not updated by FMC when recieved as part of toBody function. +func (data DeviceHAPair) toBodyUpdateTimers(ctx context.Context, state DeviceHAPair) string { + body := "" + if !data.FailedInterfacesPercent.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.percentFailedInterfaceExceed", data.FailedInterfacesPercent.ValueInt64()) + } + if !data.FailedInterfacesLimit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.noOfFailedInterfaceLimit", data.FailedInterfacesLimit.ValueInt64()) + } + if !data.PeerPollTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerPollTime", data.PeerPollTime.ValueInt64()) + } + if !data.PeerPollTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerPollTimeUnit", data.PeerPollTimeUnit.ValueString()) + } + if !data.PeerHoldTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerHoldTime", data.PeerHoldTime.ValueInt64()) + } + if !data.PeerHoldTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.peerHoldTimeUnit", data.PeerHoldTimeUnit.ValueString()) + } + if !data.InterfacePollTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfacePollTime", data.InterfacePollTime.ValueInt64()) + } + if !data.InterfacePollTimeUnit.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfacePollTimeUnit", data.InterfacePollTimeUnit.ValueString()) + } + if !data.InterfaceHoldTime.IsNull() { + body, _ = sjson.Set(body, "ftdHAFailoverTriggerCriteria.interfaceHoldTime", data.InterfaceHoldTime.ValueInt64()) + } + if body != "" && data.Id.ValueString() != "" { + body, _ = sjson.Set(body, "id", data.Id.ValueString()) + } + return body +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ebc832df..34584db8 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -299,6 +299,7 @@ func (p *FmcProvider) Resources(ctx context.Context) []func() resource.Resource NewDeviceBGPResource, NewDeviceBGPGeneralSettingsResource, NewDeviceEtherChannelInterfaceResource, + NewDeviceHAPairResource, NewDeviceHAPairMonitoringResource, NewDeviceIPv4StaticRouteResource, NewDeviceIPv6StaticRouteResource, @@ -357,6 +358,7 @@ func (p *FmcProvider) DataSources(ctx context.Context) []func() datasource.DataS NewDeviceBGPDataSource, NewDeviceBGPGeneralSettingsDataSource, NewDeviceEtherChannelInterfaceDataSource, + NewDeviceHAPairDataSource, NewDeviceHAPairMonitoringDataSource, NewDeviceIPv4StaticRouteDataSource, NewDeviceIPv6StaticRouteDataSource, diff --git a/internal/provider/resource_fmc_device_ha_pair.go b/internal/provider/resource_fmc_device_ha_pair.go new file mode 100644 index 00000000..769ddd30 --- /dev/null +++ b/internal/provider/resource_fmc_device_ha_pair.go @@ -0,0 +1,607 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public 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 +// +// https://mozilla.org/MPL/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. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "net/url" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-fmc" + "github.com/netascode/terraform-provider-fmc/internal/provider/helpers" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure provider defined types fully satisfy framework interfaces +var ( + _ resource.Resource = &DeviceHAPairResource{} + _ resource.ResourceWithImportState = &DeviceHAPairResource{} +) + +func NewDeviceHAPairResource() resource.Resource { + return &DeviceHAPairResource{} +} + +type DeviceHAPairResource struct { + client *fmc.Client +} + +func (r *DeviceHAPairResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_device_ha_pair" +} + +func (r *DeviceHAPairResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: helpers.NewAttributeDescription("Resource to manage HA Pair. failed_interfaces_limit or failed_interfaces_percent needs to be set.").String, + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "The id of the object", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "domain": schema.StringAttribute{ + MarkdownDescription: "The name of the FMC domain", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The name of the High Availability (HA) Pair.").String, + Required: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Type of the resource; This is always `DeviceHAPair`.").String, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "primary_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of primary FTD in the HA Pair.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "secondary_device_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of secondary FTD in the HA Pair.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_interface_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of High Availability Link interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_interface_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of High Availability Link interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_interface_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Type of High Availability Link interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_logical_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The logical name of failover interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_use_ipv6": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Use IPv6 addressing for HA communication.").AddDefaultValueDescription("false").String, + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "ha_link_primary_ip": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The IP of primary node interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_secondary_ip": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The IP of secondary node interface.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ha_link_netmask": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Subnet mask for HA link.").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_use_same_as_ha": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Use the same link for state and HA.").String, + Required: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "state_link_interface_id": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ID of physical interface.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_interface_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Name of state link interface.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_interface_type": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Type of state link interface.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_logical_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_use_ipv6": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Use IPv6 addressing for HA communication.").String, + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "state_link_primary_ip": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The IP of primary node interface.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_secondary_ip": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("The IP of secondary node interface.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "state_link_netmask": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Subnet mask for state link.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "encryption_enabled": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Use encryption for communication.").String, + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "encryption_key_generation_scheme": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Select the encyption key generation scheme.").AddStringEnumDescription("AUTO", "CUSTOM").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("AUTO", "CUSTOM"), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "encryption_key": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Pass shared key for encryption if CUSTOM key geneeration scheme is selected.").String, + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "failed_interfaces_percent": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Percentage of Failed Interfaces that triggers failover.").AddIntegerRangeDescription(1, 100).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 100), + }, + }, + "failed_interfaces_limit": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Number of Failed Interfaces that triggers failover.").AddIntegerRangeDescription(1, 211).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 211), + }, + }, + "peer_poll_time": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Pool Time (1-15 SEC or 200-999 MSEC)").AddIntegerRangeDescription(1, 999).AddDefaultValueDescription("1").String, + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 999), + }, + Default: int64default.StaticInt64(1), + }, + "peer_poll_time_unit": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Pool Time Unit").AddStringEnumDescription("SEC", "MSEC").AddDefaultValueDescription("SEC").String, + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("SEC", "MSEC"), + }, + Default: stringdefault.StaticString("SEC"), + }, + "peer_hold_time": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Hold Time (3-45 SEC or 800-999 MSEC)").AddIntegerRangeDescription(3, 999).AddDefaultValueDescription("15").String, + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(3, 999), + }, + Default: int64default.StaticInt64(15), + }, + "peer_hold_time_unit": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Hold Time Unit").AddStringEnumDescription("SEC", "MSEC").AddDefaultValueDescription("SEC").String, + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("SEC", "MSEC"), + }, + Default: stringdefault.StaticString("SEC"), + }, + "interface_poll_time": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Pool Time (1-15 SEC or 500-999 MSEC)").AddIntegerRangeDescription(1, 999).AddDefaultValueDescription("5").String, + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(1, 999), + }, + Default: int64default.StaticInt64(5), + }, + "interface_poll_time_unit": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Peer Pool Time Unit").AddStringEnumDescription("SEC", "MSEC").AddDefaultValueDescription("SEC").String, + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("SEC", "MSEC"), + }, + Default: stringdefault.StaticString("SEC"), + }, + "interface_hold_time": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface Hold Time in seconds").AddIntegerRangeDescription(25, 75).AddDefaultValueDescription("25").String, + Optional: true, + Computed: true, + Validators: []validator.Int64{ + int64validator.Between(25, 75), + }, + Default: int64default.StaticInt64(25), + }, + "action": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("FTD HA PUT operation action. Specifically used for manual switch. HA Break will be triggered when you run terraform destroy").AddStringEnumDescription("SWITCH", "HABREAK").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("SWITCH", "HABREAK"), + }, + }, + }, + } +} + +func (r *DeviceHAPairResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.client = req.ProviderData.(*FmcProviderData).Client +} + +// End of section. //template:end model + +func (r *DeviceHAPairResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan DeviceHAPair + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.Id.ValueString())) + + // Create object + body := plan.toBody(ctx, DeviceHAPair{}) + res, err := r.client.Post(plan.getPath(), body, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (POST/PUT), got error: %s, %s", err, res.String())) + return + } + + // Adding code to poll object + taskID := res.Get("metadata.task.id").String() + tflog.Debug(ctx, fmt.Sprintf("%s: Async task initiated successfully", taskID)) + + const atom time.Duration = 5 * time.Second + // We need device's UUID, but it only shows after the task succeeds. Poll the task. + for i := time.Duration(0); i < 5*time.Minute; i += atom { + task, err := r.client.Get("/api/fmc_config/v1/domain/{DOMAIN_UUID}/job/taskstatuses/"+url.QueryEscape(taskID), reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to read object (GET), got error: %s, %s", err, task.String())) + return + } + stat := strings.ToUpper(task.Get("status").String()) + if stat == "FAILED" { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("API task for the new HA Pair failed: %s, %s", task.Get("message"), task.Get("description"))) + return + } + if stat != "PENDING" && stat != "RUNNING" && stat != "IN_PROGRESS" { + break + } + time.Sleep(atom) + } + + check, err := r.client.Get(plan.getPath()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to read object (GET), got error: %s, %s", err, check)) + return + } + name := "items.#(name==" + url.QueryEscape(plan.Name.ValueString()) + ").id" + id := check.Get(name).String() + plan.Id = types.StringValue(id) + if plan.Id.ValueString() == "" { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("No HA Pair named %q: %s", plan.Name.ValueString(), check)) + return + } + + // Send second request to configure missing pieces + body = plan.toBodyUpdateTimers(ctx, DeviceHAPair{}) + if body != "" { + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update object (PUT) after create, got error: %s, %s", err, res.String())) + // Save state, as at this point HA Pair is created, though not fully configured + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + return + } + } + // Ending code to poll object + plan.fromBodyUnknowns(ctx, res) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (r *DeviceHAPairResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state DeviceHAPair + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !state.Domain.IsNull() && state.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.String())) + + urlPath := state.getPath() + "/" + url.QueryEscape(state.Id.ValueString()) + res, err := r.client.Get(urlPath, reqMods...) + + if err != nil && strings.Contains(err.Error(), "StatusCode 404") { + resp.State.RemoveResource(ctx) + return + } else if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (GET), got error: %s, %s", err, res.String())) + return + } + + imp, diags := helpers.IsFlagImporting(ctx, req) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // After `terraform import` we switch to a full read. + if imp { + state.fromBody(ctx, res) + } else { + state.fromBodyPartial(ctx, res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end read + +func (r *DeviceHAPairResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state DeviceHAPair + + // Read plan + diags := req.Plan.Get(ctx, &plan) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Read state + diags = req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !plan.Domain.IsNull() && plan.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(plan.Domain.ValueString())) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + // Update object 'core' + body := plan.toBody(ctx, state) + res, err := r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + // Update object 'timers' + body = plan.toBodyUpdateTimers(ctx, state) + res, err = r.client.Put(plan.getPath()+"/"+url.QueryEscape(plan.Id.ValueString()), body, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PUT), got error: %s, %s", err, res.String())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *DeviceHAPairResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state DeviceHAPair + + // Read state + diags := req.State.Get(ctx, &state) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // Set request domain if provided + reqMods := [](func(*fmc.Req)){} + if !state.Domain.IsNull() && state.Domain.ValueString() != "" { + reqMods = append(reqMods, fmc.DomainName(state.Domain.ValueString())) + } + // Start of HA Break code + body := state.toBodyPutDelete(ctx, DeviceHAPair{}) + res, err := r.client.Put(state.getPath()+"/"+url.QueryEscape(state.Id.ValueString()), body, reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to remove object configuration (PUT), got error: %s, %s", err, res.String())) + return + } + // End of HA Break code + + // Adding code to poll object + taskID := res.Get("metadata.task.id").String() + tflog.Debug(ctx, fmt.Sprintf("%s: Async task initiated successfully", taskID)) + + const atom time.Duration = 5 * time.Second + // We need device's UUID, but it only shows after the task succeeds. Poll the task. + for i := time.Duration(0); i < 5*time.Minute; i += atom { + task, err := r.client.Get("/api/fmc_config/v1/domain/{DOMAIN_UUID}/job/taskstatuses/"+url.QueryEscape(taskID), reqMods...) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to read object (GET), got error: %s, %s", err, task.String())) + return + } + stat := strings.ToUpper(task.Get("status").String()) + if stat == "FAILED" { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("API task for the new device failed: %s, %s", task.Get("message"), task.Get("description"))) + return + } + if stat != "PENDING" && stat != "RUNNING" && stat != "IN_PROGRESS" { + break + } + time.Sleep(atom) + } + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +// Section below is generated&owned by "gen/generator.go". //template:begin import + +func (r *DeviceHAPairResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import + +// Section below is generated&owned by "gen/generator.go". //template:begin createSubresources + +// End of section. //template:end createSubresources + +// Section below is generated&owned by "gen/generator.go". //template:begin deleteSubresources + +// End of section. //template:end deleteSubresources + +// Section below is generated&owned by "gen/generator.go". //template:begin updateSubresources + +// End of section. //template:end updateSubresources diff --git a/internal/provider/resource_fmc_device_ha_pair_test.go b/internal/provider/resource_fmc_device_ha_pair_test.go new file mode 100644 index 00000000..3860ae95 --- /dev/null +++ b/internal/provider/resource_fmc_device_ha_pair_test.go @@ -0,0 +1,154 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public 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 +// +// https://mozilla.org/MPL/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. +// +// SPDX-License-Identifier: MPL-2.0 + +package provider + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAcc + +func TestAccFmcDeviceHAPair(t *testing.T) { + if os.Getenv("TF_VAR_device_id") == "" || os.Getenv("TF_VAR_device_2_id") == "" { + t.Skip("skipping test, set environment variable TF_VAR_device_id and TF_VAR_device_2_id") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "name", "FTD_HA")) + checks = append(checks, resource.TestCheckResourceAttrSet("fmc_device_ha_pair.test", "type")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_interface_name", "GigabitEthernet0/0")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_logical_name", "LAN-INTERFACE")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_use_ipv6", "false")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_primary_ip", "1.1.1.1")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_secondary_ip", "1.1.1.2")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "ha_link_netmask", "255.255.255.0")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_interface_name", "GigabitEthernet0/0")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_logical_name", "Stateful-INTERFACE")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_use_ipv6", "false")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_primary_ip", "10.10.10.1")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_secondary_ip", "10.10.10.2")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "state_link_netmask", "255.255.255.0")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "encryption_enabled", "true")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "failed_interfaces_limit", "1")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "peer_poll_time", "1")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "peer_poll_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "peer_hold_time", "15")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "peer_hold_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "interface_poll_time", "5")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "interface_poll_time_unit", "SEC")) + checks = append(checks, resource.TestCheckResourceAttr("fmc_device_ha_pair.test", "interface_hold_time", "25")) + + var steps []resource.TestStep + if os.Getenv("SKIP_MINIMUM_TEST") == "" { + steps = append(steps, resource.TestStep{ + Config: testAccFmcDeviceHAPairPrerequisitesConfig + testAccFmcDeviceHAPairConfig_minimum(), + }) + } + steps = append(steps, resource.TestStep{ + Config: testAccFmcDeviceHAPairPrerequisitesConfig + testAccFmcDeviceHAPairConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }) + steps = append(steps, resource.TestStep{ + ResourceName: "fmc_device_ha_pair.test", + ImportState: true, + }) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + ErrorCheck: func(err error) error { return testAccErrorCheck(t, err) }, + Steps: steps, + }) +} + +// End of section. //template:end testAcc + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites + +const testAccFmcDeviceHAPairPrerequisitesConfig = ` +variable "device_id" { default = null } // tests will set $TF_VAR_device_id +variable "device_2_id" { default = null } // tests will set $TF_VAR_device_2_id +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccFmcDeviceHAPairConfig_minimum() string { + config := `resource "fmc_device_ha_pair" "test" {` + "\n" + config += ` name = "FTD_HA"` + "\n" + config += ` primary_device_id = var.device_id` + "\n" + config += ` secondary_device_id = var.device_2_id` + "\n" + config += ` ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482"` + "\n" + config += ` ha_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` ha_link_interface_type = ""` + "\n" + config += ` ha_link_logical_name = "LAN-INTERFACE"` + "\n" + config += ` ha_link_primary_ip = "1.1.1.1"` + "\n" + config += ` ha_link_secondary_ip = "1.1.1.2"` + "\n" + config += ` ha_link_netmask = "255.255.255.0"` + "\n" + config += ` state_link_use_same_as_ha = false` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccFmcDeviceHAPairConfig_all() string { + config := `resource "fmc_device_ha_pair" "test" {` + "\n" + config += ` name = "FTD_HA"` + "\n" + config += ` primary_device_id = var.device_id` + "\n" + config += ` secondary_device_id = var.device_2_id` + "\n" + config += ` ha_link_interface_id = "96d24097-41c4-4332-a4d0-a8c07ac08482"` + "\n" + config += ` ha_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` ha_link_interface_type = ""` + "\n" + config += ` ha_link_logical_name = "LAN-INTERFACE"` + "\n" + config += ` ha_link_use_ipv6 = false` + "\n" + config += ` ha_link_primary_ip = "1.1.1.1"` + "\n" + config += ` ha_link_secondary_ip = "1.1.1.2"` + "\n" + config += ` ha_link_netmask = "255.255.255.0"` + "\n" + config += ` state_link_use_same_as_ha = false` + "\n" + config += ` state_link_interface_id = "76d24097-hj7r-7786-a4d0-a8c07ac08470"` + "\n" + config += ` state_link_interface_name = "GigabitEthernet0/0"` + "\n" + config += ` state_link_interface_type = "PhysicalInterface"` + "\n" + config += ` state_link_logical_name = "Stateful-INTERFACE"` + "\n" + config += ` state_link_use_ipv6 = false` + "\n" + config += ` state_link_primary_ip = "10.10.10.1"` + "\n" + config += ` state_link_secondary_ip = "10.10.10.2"` + "\n" + config += ` state_link_netmask = "255.255.255.0"` + "\n" + config += ` encryption_enabled = true` + "\n" + config += ` encryption_key_generation_scheme = "AUTO"` + "\n" + config += ` failed_interfaces_limit = 1` + "\n" + config += ` peer_poll_time = 1` + "\n" + config += ` peer_poll_time_unit = "SEC"` + "\n" + config += ` peer_hold_time = 15` + "\n" + config += ` peer_hold_time_unit = "SEC"` + "\n" + config += ` interface_poll_time = 5` + "\n" + config += ` interface_poll_time_unit = "SEC"` + "\n" + config += ` interface_hold_time = 25` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll