diff --git a/docs/data-sources/device_cluster.md b/docs/data-sources/device_cluster.md new file mode 100644 index 00000000..fe5bf8a0 --- /dev/null +++ b/docs/data-sources/device_cluster.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_cluster Data Source - terraform-provider-fmc" +subcategory: "" +description: |- + Data source for FTD Device Cluster in FMC + An example is shown below: + hcl + data "fmc_device_cluster" "cluster" { + name = "ftd-cluster1" + } +--- + +# fmc_device_cluster (Data Source) + +Data source for FTD Device Cluster in FMC + +An example is shown below: +```hcl +data "fmc_device_cluster" "cluster" { + name = "ftd-cluster1" +} +``` + + + + +## Schema + +### Required + +- `name` (String) Name of the cluster + +### Read-Only + +- `id` (String) The ID of this resource. +- `type` (String) Type of this resource + + diff --git a/docs/data-sources/device_physical_interfaces.md b/docs/data-sources/device_physical_interfaces.md index 25bc991c..4457dd74 100644 --- a/docs/data-sources/device_physical_interfaces.md +++ b/docs/data-sources/device_physical_interfaces.md @@ -7,6 +7,7 @@ description: |- An example is shown below: hcl data "fmc_device_physical_interfaces" "test-phy-interfaces" { + device_id = "" name = "TEST-PHY" } --- @@ -18,6 +19,7 @@ Data source for Physical Interfaces in FMC An example is shown below: ```hcl data "fmc_device_physical_interfaces" "test-phy-interfaces" { + device_id = "" name = "TEST-PHY" } ``` diff --git a/docs/data-sources/device_subinterfaces.md b/docs/data-sources/device_subinterfaces.md new file mode 100644 index 00000000..4115db9a --- /dev/null +++ b/docs/data-sources/device_subinterfaces.md @@ -0,0 +1,48 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_subinterfaces Data Source - terraform-provider-fmc" +subcategory: "" +description: |- + Data source for Sub-Interfaces in FMC + An example is shown below: + hcl + data "fmc_device_subinterfaces" "sub_interfaces" { + device_id = "" + subinterface_id = 1 + } +--- + +# fmc_device_subinterfaces (Data Source) + +Data source for Sub-Interfaces in FMC + +An example is shown below: +```hcl +data "fmc_device_subinterfaces" "sub_interfaces" { + device_id = "" + subinterface_id = 1 +} +``` + + + + +## Schema + +### Required + +- `device_id` (String) The ID of the device this resource needs +- `subinterface_id` (Number) ID of sub interface + +### Read-Only + +- `id` (String) The ID of this resource. +- `if_name` (String) SubInterface logical name +- `ipv4_static_address` (String) IPv4 Static address +- `mode` (String) The mode of this resource +- `mtu` (Number) SubInterface MTU +- `security_zone_id` (String) Security Zone ID +- `type` (String) SubInterface type +- `vlan_id` (Number) The Vlan ID needed for this resource + + diff --git a/docs/data-sources/port_group_objects.md b/docs/data-sources/port_group_objects.md new file mode 100644 index 00000000..011e8055 --- /dev/null +++ b/docs/data-sources/port_group_objects.md @@ -0,0 +1,38 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_port_group_objects Data Source - terraform-provider-fmc" +subcategory: "" +description: |- + Data source for Port Group Objects in FMC + An example is shown below: + hcl + data "fmc_port_group_objects" "test" { + name = "test-object-group" + } +--- + +# fmc_port_group_objects (Data Source) + +Data source for Port Group Objects in FMC + +An example is shown below: +```hcl +data "fmc_port_group_objects" "test" { + name = "test-object-group" +} +``` + + + + +## Schema + +### Required + +- `name` (String) Name of the port group object + +### Read-Only + +- `id` (String) The ID of this resource. + + diff --git a/docs/resources/device_cluster.md b/docs/resources/device_cluster.md new file mode 100644 index 00000000..db96f401 --- /dev/null +++ b/docs/resources/device_cluster.md @@ -0,0 +1,220 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_cluster Resource - terraform-provider-fmc" +subcategory: "" +description: |- + Resource for adding device in a cluster + Note: this will only work on VMware, not on public cloud. + Example + An example is shown below: + hcl + resource "fmc_device_cluster" "cluster" { + name = "ftd_cluster" + control_device { + cluster_node_bootstrap { + priority = 1 + cclip = "10.10.11.1" + } + device_details { + id = data.fmc_devices.device1.id + name = data.fmc_devices.device1.name + } + } + common_bootstrap { + ccl_interface { + id = data.fmc_device_physical_interfaces.ccl_physical_interface.id + name = data.fmc_device_physical_interfaces.ccl_physical_interface.name + } + ccl_network = "10.10.11.0/27" + vni_network = "10.10.10.0/27" + } + data_devices{ + cluster_node_bootstrap{ + priority = 2 + cclip = "10.10.11.2" + } + device_details{ + id = data.fmc_devices.device2.id + name = data.fmc_devices.device2.name + } + } + data_devices{ + cluster_node_bootstrap{ + priority = 3 + cclip = "10.10.11.3" + } + device_details{ + id = data.fmc_devices.device3.id + name = data.fmc_devices.device3.name + } + } + } + **Note:** This feature is only supported for VMWare cloud platform. + **Note:** If creating multiple rules during a single `terraform apply`, remember to use `depends_on` to chain the rules so that terraform creates it in the same order that you intended. + **Note:** Deleting a cluster will delete all it's data nodes with control node as well. +--- + +# fmc_device_cluster (Resource) + +Resource for adding device in a cluster + +**Note: this will only work on VMware, not on public cloud.** +## Example +An example is shown below: +```hcl + resource "fmc_device_cluster" "cluster" { + name = "ftd_cluster" + control_device { + cluster_node_bootstrap { + priority = 1 + cclip = "10.10.11.1" + } + device_details { + id = data.fmc_devices.device1.id + name = data.fmc_devices.device1.name + } + } + common_bootstrap { + ccl_interface { + id = data.fmc_device_physical_interfaces.ccl_physical_interface.id + name = data.fmc_device_physical_interfaces.ccl_physical_interface.name + } + ccl_network = "10.10.11.0/27" + vni_network = "10.10.10.0/27" + } + data_devices{ + cluster_node_bootstrap{ + priority = 2 + cclip = "10.10.11.2" + } + device_details{ + id = data.fmc_devices.device2.id + name = data.fmc_devices.device2.name + } + } + data_devices{ + cluster_node_bootstrap{ + priority = 3 + cclip = "10.10.11.3" + } + device_details{ + id = data.fmc_devices.device3.id + name = data.fmc_devices.device3.name + } + } + } +**Note:** This feature is only supported for VMWare cloud platform. +**Note:** If creating multiple rules during a single `terraform apply`, remember to use `depends_on` to chain the rules so that terraform creates it in the same order that you intended. +**Note:** Deleting a cluster will delete all it's data nodes with control node as well. +``` + + + + +## Schema + +### Required + +- `common_bootstrap` (Block List, Min: 1) Control device information (see [below for nested schema](#nestedblock--common_bootstrap)) +- `control_device` (Block List, Min: 1) Control device information (see [below for nested schema](#nestedblock--control_device)) + +### Optional + +- `data_devices` (Block List, Max: 500) Data device information (see [below for nested schema](#nestedblock--data_devices)) +- `name` (String) The name of cluster + +### Read-Only + +- `id` (String) The ID of this resource. +- `type` (String) The type of this resource + + +### Nested Schema for `common_bootstrap` + +Optional: + +- `ccl_interface` (Block List) (see [below for nested schema](#nestedblock--common_bootstrap--ccl_interface)) +- `ccl_network` (String) Cluster control network +- `vni_network` (String) VNI network + + +### Nested Schema for `common_bootstrap.ccl_interface` + +Optional: + +- `id` (String) Interface ID +- `name` (String) Interface Name + +Read-Only: + +- `type` (String) Interface Type + + + + +### Nested Schema for `control_device` + +Required: + +- `cluster_node_bootstrap` (Block List, Min: 1) (see [below for nested schema](#nestedblock--control_device--cluster_node_bootstrap)) +- `device_details` (Block List, Min: 1) (see [below for nested schema](#nestedblock--control_device--device_details)) + + +### Nested Schema for `control_device.cluster_node_bootstrap` + +Required: + +- `cclip` (String) Cluster control IP + +Optional: + +- `priority` (Number) Set the priority of cluster node + + + +### Nested Schema for `control_device.device_details` + +Required: + +- `id` (String) Device ID + +Optional: + +- `name` (String) Device Name + +Read-Only: + +- `type` (String) Device Type + + + + +### Nested Schema for `data_devices` + +Optional: + +- `cluster_node_bootstrap` (Block List) (see [below for nested schema](#nestedblock--data_devices--cluster_node_bootstrap)) +- `device_details` (Block List) (see [below for nested schema](#nestedblock--data_devices--device_details)) + + +### Nested Schema for `data_devices.cluster_node_bootstrap` + +Optional: + +- `cclip` (String) Cluster control IP +- `priority` (Number) Set the priority of cluster node + + + +### Nested Schema for `data_devices.device_details` + +Optional: + +- `id` (String) Device ID +- `name` (String) Device Name + +Read-Only: + +- `type` (String) Device Type + + diff --git a/docs/resources/device_subinterfaces.md b/docs/resources/device_subinterfaces.md new file mode 100644 index 00000000..d112ada1 --- /dev/null +++ b/docs/resources/device_subinterfaces.md @@ -0,0 +1,92 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_device_subinterfaces Resource - terraform-provider-fmc" +subcategory: "" +description: |- + Resource for SubInterfaces in FMC + Example + An example is shown below: + hcl + resource "fmc_device_subinterfaces" "sub" { + name = "GigabitEthernet0/1" + device_id = "" + subinterface_id = 1 + vlan_id = 30 + security_zone_id = "" + if_name = "Inside" + mtu = 1700 + mode = "NONE" + enabled = true + ipv4_static_address = "10.20.220.45" + ipv4_static_netmask = 24 + ipv4_dhcp_enabled = false + ipv4_dhcp_route_metric = 1 + ipv6_address = "2001:10:240:ac::a" + ipv6_prefix = 124 + ipv6_enforce_eui = false + } +--- + +# fmc_device_subinterfaces (Resource) + +Resource for SubInterfaces in FMC + +## Example +An example is shown below: +```hcl +resource "fmc_device_subinterfaces" "sub" { + name = "GigabitEthernet0/1" + device_id = "" + subinterface_id = 1 + vlan_id = 30 + security_zone_id = "" + if_name = "Inside" + mtu = 1700 + mode = "NONE" + enabled = true + ipv4_static_address = "10.20.220.45" + ipv4_static_netmask = 24 + ipv4_dhcp_enabled = false + ipv4_dhcp_route_metric = 1 + ipv6_address = "2001:10:240:ac::a" + ipv6_prefix = 124 + ipv6_enforce_eui = false +} +``` + + + + +## Schema + +### Required + +- `device_id` (String) The ID of the device this resource needs +- `name` (String) Name of the physical interface +- `subinterface_id` (Number) The ID this resource needs + +### Optional + +- `enable_ipv6` (Boolean) IPv6 address enabled +- `enabled` (Boolean) Enable this resource +- `ifname` (String) Name of chosen interface +- `ipv4_dhcp_enabled` (Boolean) IPv4 DHCP enabled +- `ipv4_dhcp_route_metric` (Number) IPv4 DHCP Route Metric +- `ipv4_static_address` (String) IPv4 Static address +- `ipv4_static_netmask` (Number) IPv4 Static address netmask +- `ipv6_address` (String) IPv6 address +- `ipv6_enforce_eui` (Boolean) IPv6 EnforceEUI64 +- `ipv6_prefix` (Number) IPv6 netmask +- `management_only` (Boolean) Managenment only or not +- `mode` (String) The mode of this resource +- `mtu` (Number) The mtu value +- `priority` (Number) The type of this resource +- `security_zone_id` (String) Security Zone ID +- `vlan_id` (Number) The Vlan ID needed for this resource + +### Read-Only + +- `id` (String) The ID of this resource. +- `type` (String) The type of this resource + + diff --git a/docs/resources/devices.md b/docs/resources/devices.md index df22c325..e108eac6 100644 --- a/docs/resources/devices.md +++ b/docs/resources/devices.md @@ -11,7 +11,6 @@ description: |- name = "ftd" hostname = "" regkey = "" - metric_value = 22 license_caps = [ "MALWARE" ] @@ -35,7 +34,6 @@ resource "fmc_devices" "device1" { name = "ftd" hostname = "" regkey = "" - metric_value = 22 license_caps = [ "MALWARE" ] @@ -60,6 +58,8 @@ resource "fmc_devices" "device1" { ### Optional +- `cdo_host` (String) CDO-Host +- `cdo_region` (String) CDO-Region - `license_caps` (List of String) License caps for this resource - `name` (String) The name of FTD - `nat_id` (String) NAT_ID is required, if configured in FTD diff --git a/docs/resources/devices_bulk.md b/docs/resources/devices_bulk.md new file mode 100644 index 00000000..09cfb3ef --- /dev/null +++ b/docs/resources/devices_bulk.md @@ -0,0 +1,128 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_devices_bulk Resource - terraform-provider-fmc" +subcategory: "" +description: |- + Resource for adding bulk devices in FMC + Example + An example is shown below: + hcl + resource "fmc_devices_bulk" "devices" { + devices { + name = "ftd" + hostname = "" + regkey = "" + license_caps = [ + "MALWARE" + ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } + devices { + name = "ftd2" + hostname = "" + regkey = "" + license_caps = [ + "MALWARE" + ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } + } + + Note: If creating multiple rules during a single terraform apply, remember to use depends_on to chain the rules so that terraform creates it in the same order that you intended. +--- + +# fmc_devices_bulk (Resource) + +Resource for adding bulk devices in FMC + +## Example +An example is shown below: +```hcl +resource "fmc_devices_bulk" "devices" { + devices { + name = "ftd" + hostname = "" + regkey = "" + license_caps = [ + "MALWARE" + ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } + devices { + name = "ftd2" + hostname = "" + regkey = "" + license_caps = [ + "MALWARE" + ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } +} +``` +**Note:** If creating multiple rules during a single `terraform apply`, remember to use `depends_on` to chain the rules so that terraform creates it in the same order that you intended. + + + + +## Schema + +### Required + +- `devices` (Block List, Min: 1, Max: 100) The list of network objects to create (see [below for nested schema](#nestedblock--devices)) + +### Read-Only + +- `id` (String) The ID of this resource. +- `id_mappings` (List of Object) id array (see [below for nested schema](#nestedatt--id_mappings)) + + +### Nested Schema for `devices` + +Required: + +- `access_policy` (Block List, Min: 1, Max: 1) access policy for this resource (see [below for nested schema](#nestedblock--devices--access_policy)) +- `hostname` (String) The hostname of FTD +- `regkey` (String) Same regkey as entered in FTD + +Optional: + +- `license_caps` (List of String) License caps for this resource +- `name` (String) The name of FTD +- `nat_id` (String) NAT_ID is required, if configured in FTD +- `performance_tier` (String) Select the desired performace tier + +Read-Only: + +- `id` (String) The id of this resource +- `type` (String) The type of this resource + + +### Nested Schema for `devices.access_policy` + +Required: + +- `id` (String) The ID of this resource + +Optional: + +- `type` (String) The type of this resource + + + + +### Nested Schema for `id_mappings` + +Read-Only: + +- `id` (String) +- `name` (String) + + diff --git a/docs/resources/network_objects_bulk.md b/docs/resources/network_objects_bulk.md new file mode 100644 index 00000000..4f3ce7c8 --- /dev/null +++ b/docs/resources/network_objects_bulk.md @@ -0,0 +1,87 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "fmc_network_objects_bulk Resource - terraform-provider-fmc" +subcategory: "" +description: |- + Resource for Network Objects in FMC + Example + An example is shown below: + hcl + locals { + object_names = [for i in range(3) : "VLAN825-Private-DRsite-${i}"] + } + resource "fmc_network_objects_bulk" "test" { + dynamic "objects" { + for_each = local.object_names + content { + name = objects.value + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" + } + } + } +--- + +# fmc_network_objects_bulk (Resource) + +Resource for Network Objects in FMC + +## Example +An example is shown below: +```hcl +locals { +object_names = [for i in range(3) : "VLAN825-Private-DRsite-${i}"] +} +resource "fmc_network_objects_bulk" "test" { +dynamic "objects" { +for_each = local.object_names +content { + name = objects.value + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" +} +} +} +``` + + + + +## Schema + +### Required + +- `objects` (Block List, Min: 1, Max: 100) The list of network objects to create (see [below for nested schema](#nestedblock--objects)) + +### Read-Only + +- `id` (String) The ID of this resource. +- `id_mappings` (List of Object) id array (see [below for nested schema](#nestedatt--id_mappings)) + + +### Nested Schema for `objects` + +Required: + +- `name` (String) The name of this resource +- `value` (String) The value of this resource + +Optional: + +- `description` (String) The description of this resource + +Read-Only: + +- `id` (String) The id of this resource +- `type` (String) The type this resource + + + +### Nested Schema for `id_mappings` + +Read-Only: + +- `id` (String) +- `name` (String) + + diff --git a/examples/fmc_access_policies/terraform.tfvars.example b/examples/fmc_access_policies/terraform.tfvars.example index cad6374c..97590cb2 100644 --- a/examples/fmc_access_policies/terraform.tfvars.example +++ b/examples/fmc_access_policies/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false \ No newline at end of file diff --git a/examples/fmc_access_policies_category/terraform.tfvars.example b/examples/fmc_access_policies_category/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_access_policies_category/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_access_rules/terraform.tfvars.example b/examples/fmc_access_rules/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_access_rules/terraform.tfvars.example +++ b/examples/fmc_access_rules/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_device_bulk/main.tf b/examples/fmc_device_bulk/main.tf new file mode 100644 index 00000000..d238c8ef --- /dev/null +++ b/examples/fmc_device_bulk/main.tf @@ -0,0 +1,43 @@ +terraform { + required_providers { + fmc = { + source = "CiscoDevNet/fmc" + # version = "0.2.0" + } + } +} + +provider "fmc" { + fmc_username = var.fmc_username + fmc_password = var.fmc_password + fmc_host = var.fmc_host + fmc_insecure_skip_verify = var.fmc_insecure_skip_verify +} + +data "fmc_access_policies" "access_policy"{ + name="test-acp" +} + +resource "fmc_devices_bulk" "device"{ + devices { + name = "FTD1" + hostname = "" + regkey = "" + performance_tier = "FTDv30" + license_caps = [ "MALWARE" ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } + + devices { + name = "FTD2" + hostname = "" + regkey = "" + performance_tier = "FTDv30" + license_caps = [ "MALWARE" ] + access_policy { + id = data.fmc_access_policies.access_policy.id + } + } +} \ No newline at end of file diff --git a/examples/fmc_device_bulk/terraform.tfvars.example b/examples/fmc_device_bulk/terraform.tfvars.example new file mode 100644 index 00000000..97590cb2 --- /dev/null +++ b/examples/fmc_device_bulk/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false \ No newline at end of file diff --git a/examples/fmc_device_bulk/var.tf b/examples/fmc_device_bulk/var.tf new file mode 100644 index 00000000..47e4e5aa --- /dev/null +++ b/examples/fmc_device_bulk/var.tf @@ -0,0 +1,18 @@ +variable "fmc_username" { + type = string + sensitive = true +} + +variable "fmc_password" { + type = string + sensitive = true +} + +variable "fmc_host" { + type = string +} + +variable "fmc_insecure_skip_verify" { + type = bool + default = false +} \ No newline at end of file diff --git a/examples/fmc_device_cluster/main.tf b/examples/fmc_device_cluster/main.tf new file mode 100644 index 00000000..12e36078 --- /dev/null +++ b/examples/fmc_device_cluster/main.tf @@ -0,0 +1,82 @@ +terraform { + required_providers { + fmc = { + source = "CiscoDevNet/fmc" + # version = "0.1.1" + } + } +} + +provider "fmc" { + fmc_username = var.fmc_username + fmc_password = var.fmc_password + fmc_host = var.fmc_host + fmc_insecure_skip_verify = var.fmc_insecure_skip_verify +} + +# data "fmc_device_cluster" "inside" { +# name = "ftd_1" +# } +data "fmc_devices" "device1" { + name = "FTD1" +} +data "fmc_devices" "device2" { + name = "FTD2" +} +data "fmc_devices" "device3" { + name = "FTD3" +} +data "fmc_device_physical_interfaces" "ccl_physical_interface" { + device_id = data.fmc_devices.device1.id + name = "GigabitEthernet0/0" +} + +# Note this will only work on VMware, not on public cloud. +resource "fmc_device_cluster" "cluster" { + name = "test-cluster" + control_device { + cluster_node_bootstrap { + priority = 1 + cclip = "10.10.11.1" + } + device_details { + id = data.fmc_devices.device1.id + name = data.fmc_devices.device1.name + } + } + common_bootstrap { + ccl_interface { + id = data.fmc_device_physical_interfaces.ccl_physical_interface.id + name = data.fmc_device_physical_interfaces.ccl_physical_interface.name + } + ccl_network = "10.10.11.0/27" + vni_network = "10.10.10.0/27" + } + + data_devices{ + cluster_node_bootstrap{ + priority = 2 + cclip = "10.10.11.2" + } + device_details{ + id = data.fmc_devices.device2.id + name = data.fmc_devices.device2.name + } + } + data_devices{ + cluster_node_bootstrap{ + priority = 3 + cclip = "10.10.11.3" + } + device_details{ + id = data.fmc_devices.device3.id + name = data.fmc_devices.device3.name + } + } +} +output "data_cluster"{ + value = fmc_device_cluster.cluster +} +# output "data_cluster"{ +# value = data.fmc_device_cluster.inside +# } diff --git a/examples/fmc_device_cluster/terraform.tfvars.example b/examples/fmc_device_cluster/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_device_cluster/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_device_cluster/var.tf b/examples/fmc_device_cluster/var.tf new file mode 100644 index 00000000..47e4e5aa --- /dev/null +++ b/examples/fmc_device_cluster/var.tf @@ -0,0 +1,18 @@ +variable "fmc_username" { + type = string + sensitive = true +} + +variable "fmc_password" { + type = string + sensitive = true +} + +variable "fmc_host" { + type = string +} + +variable "fmc_insecure_skip_verify" { + type = bool + default = false +} \ No newline at end of file diff --git a/examples/fmc_device_sub_interfaces/main.tf b/examples/fmc_device_sub_interfaces/main.tf new file mode 100644 index 00000000..2e88507c --- /dev/null +++ b/examples/fmc_device_sub_interfaces/main.tf @@ -0,0 +1,61 @@ +terraform { + required_providers { + fmc = { + source = "CiscoDevNet/fmc" + # version = "0.2.0" + } + } +} + +provider "fmc" { + fmc_username = var.fmc_username + fmc_password = var.fmc_password + fmc_host = var.fmc_host + fmc_insecure_skip_verify = var.fmc_insecure_skip_verify +} + +data "fmc_devices" "device" { + name = "FTD1" +} + +resource "fmc_security_zone" "outside" { + name = "outside" + interface_mode = "ROUTED" +} + + +# data "fmc_device_subinterfaces" "sub_interfaces" { +# device_id = data.fmc_devices.device.id +# subinterface_id = 12 # Example: Represented by the integer value after ' . ' in GigabitEthernet0/1.12 +# } + +resource "fmc_device_subinterfaces" "sub" { + device_id = data.fmc_devices.device.id + ifname = "Testing12" + subinterface_id = 12345 + vlan_id = 80 + name = "GigabitEthernet0/1" + mode = "NONE" + mtu = 1600 + enabled = true + priority = 69 + security_zone_id = fmc_security_zone.outside.id + ipv4_dhcp_enabled = false + ipv4_dhcp_route_metric = 1 + + enable_ipv6 = true + ipv6_address = "2001:10:240:ac::a" + ipv6_prefix = "124" + ipv6_enforce_eui = false + +} + +output "new_subinterface_sub" { + value = fmc_device_subinterfaces.sub +} + +# output "old_physical_interfaces" { +# value = data.fmc_device_subinterfaces.sub_interfaces +# } + + diff --git a/examples/fmc_device_sub_interfaces/terraform.tfvars.example b/examples/fmc_device_sub_interfaces/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_device_sub_interfaces/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_device_sub_interfaces/var.tf b/examples/fmc_device_sub_interfaces/var.tf new file mode 100644 index 00000000..47e4e5aa --- /dev/null +++ b/examples/fmc_device_sub_interfaces/var.tf @@ -0,0 +1,18 @@ +variable "fmc_username" { + type = string + sensitive = true +} + +variable "fmc_password" { + type = string + sensitive = true +} + +variable "fmc_host" { + type = string +} + +variable "fmc_insecure_skip_verify" { + type = bool + default = false +} \ No newline at end of file diff --git a/examples/fmc_devices/main.tf b/examples/fmc_devices/main.tf index f1c5654c..2e5ccce8 100644 --- a/examples/fmc_devices/main.tf +++ b/examples/fmc_devices/main.tf @@ -26,16 +26,16 @@ data "fmc_access_policies" "access_policy"{ name="test-acp" } -resource "fmc_devices" "device"{ - name = "FTD" - hostname = "" +resource "fmc_devices" "device" { + name = "FTD-1" + hostname = "172.16.0.10" regkey = "cisco" performance_tier = "FTDv30" - license_caps = [ "MALWARE"] + license_caps = [ "MALWARE" ] access_policy { id = data.fmc_access_policies.access_policy.id - } -} + } +} output "fmc_devicess" { value = fmc_devices.device diff --git a/examples/fmc_devices/terraform.tfvars.example b/examples/fmc_devices/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_devices/terraform.tfvars.example +++ b/examples/fmc_devices/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_dynamic_object/terraform.tfvars.example b/examples/fmc_dynamic_object/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_dynamic_object/terraform.tfvars.example +++ b/examples/fmc_dynamic_object/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_dynamic_object_mapping/terraform.tfvars.example b/examples/fmc_dynamic_object_mapping/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_dynamic_object_mapping/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_extended_acl/terraform.tfvars.example b/examples/fmc_extended_acl/terraform.tfvars.example index f06e91c6..06bae73f 100644 --- a/examples/fmc_extended_acl/terraform.tfvars.example +++ b/examples/fmc_extended_acl/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_file_policies/terraform.tfvars.example b/examples/fmc_file_policies/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_file_policies/terraform.tfvars.example +++ b/examples/fmc_file_policies/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_fqdn_objects/terraform.tfvars.example b/examples/fmc_fqdn_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_fqdn_objects/terraform.tfvars.example +++ b/examples/fmc_fqdn_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_ftd_autonat_rules/terraform.tfvars.example b/examples/fmc_ftd_autonat_rules/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_ftd_autonat_rules/terraform.tfvars.example +++ b/examples/fmc_ftd_autonat_rules/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_ftd_deploy/terraform.tfvars.example b/examples/fmc_ftd_deploy/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_ftd_deploy/terraform.tfvars.example +++ b/examples/fmc_ftd_deploy/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_ftd_manualnat_rules/terraform.tfvars.example b/examples/fmc_ftd_manualnat_rules/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_ftd_manualnat_rules/terraform.tfvars.example +++ b/examples/fmc_ftd_manualnat_rules/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_ftd_nat_policies/terraform.tfvars.example b/examples/fmc_ftd_nat_policies/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_ftd_nat_policies/terraform.tfvars.example +++ b/examples/fmc_ftd_nat_policies/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_host_objects/terraform.tfvars.example b/examples/fmc_host_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_host_objects/terraform.tfvars.example +++ b/examples/fmc_host_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_icmpv4_objects/terraform.tfvars.example b/examples/fmc_icmpv4_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_icmpv4_objects/terraform.tfvars.example +++ b/examples/fmc_icmpv4_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_ips_policies/terraform.tfvars.example b/examples/fmc_ips_policies/terraform.tfvars.example index ef9e29a4..06bae73f 100644 --- a/examples/fmc_ips_policies/terraform.tfvars.example +++ b/examples/fmc_ips_policies/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "admin" -fmc_password = "Password@123" -fmc_host = "0.0.0.0" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_network_group_objects/terraform.tfvars.example b/examples/fmc_network_group_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_network_group_objects/terraform.tfvars.example +++ b/examples/fmc_network_group_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_network_objects/main.tf b/examples/fmc_network_objects/main.tf index e7770b9d..f571e4e6 100644 --- a/examples/fmc_network_objects/main.tf +++ b/examples/fmc_network_objects/main.tf @@ -14,6 +14,10 @@ provider "fmc" { fmc_insecure_skip_verify = var.fmc_insecure_skip_verify } +locals { + object_names = [for i in range(5): "test-${i}"] +} + data "fmc_network_objects" "PrivateVLAN" { name = "VLAN825-Private" } @@ -24,6 +28,38 @@ resource "fmc_network_objects" "PrivateVLANDR" { description = "testing terraform" } +/* resource "fmc_network_objects_bulk" "tests" { + dynamic "objects" { + for_each = local.object_names + + content { + name = objects.value + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" + } + } +} */ + +resource "fmc_network_objects_bulk" "test" { + objects { + name = "test-1" + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" + } + + objects { + name = "test-2" + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" + } + + objects { + name = "test-3" + value = data.fmc_network_objects.PrivateVLAN.value + description = "testing terraform" + } +} + output "existing_fmc_network_object" { value = data.fmc_network_objects.PrivateVLAN } diff --git a/examples/fmc_network_objects/terraform.tfvars.example b/examples/fmc_network_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_network_objects/terraform.tfvars.example +++ b/examples/fmc_network_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_nw_analysis_policies/terraform.tfvars.example b/examples/fmc_nw_analysis_policies/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_nw_analysis_policies/terraform.tfvars.example +++ b/examples/fmc_nw_analysis_policies/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_physical_interface/terraform.tfvars.example b/examples/fmc_physical_interface/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_physical_interface/terraform.tfvars.example +++ b/examples/fmc_physical_interface/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_policy_devices_assignments/terraform.tfvars.example b/examples/fmc_policy_devices_assignments/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_policy_devices_assignments/terraform.tfvars.example +++ b/examples/fmc_policy_devices_assignments/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_port_group_objects/main.tf b/examples/fmc_port_group_objects/main.tf index 55eb8934..31397e74 100644 --- a/examples/fmc_port_group_objects/main.tf +++ b/examples/fmc_port_group_objects/main.tf @@ -14,6 +14,9 @@ provider "fmc" { fmc_insecure_skip_verify = var.fmc_insecure_skip_verify } +data "fmc_port_group_objects" "test"{ + name = "test" +} resource "fmc_port_objects" "shbharti_port_1" { name = "shbharti_test_port_object_1" port = "3943" @@ -43,4 +46,8 @@ resource "fmc_port_group_objects" "TestPortGroup" { output "new_fmc_port_group_object" { value = fmc_port_group_objects.TestPortGroup +} + +output "TEST" { + value = data.fmc_port_group_objects.test } \ No newline at end of file diff --git a/examples/fmc_port_group_objects/terraform.tfvars.example b/examples/fmc_port_group_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_port_group_objects/terraform.tfvars.example +++ b/examples/fmc_port_group_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_port_objects/terraform.tfvars.example b/examples/fmc_port_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_port_objects/terraform.tfvars.example +++ b/examples/fmc_port_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_prefilter_policy/terraform.tfvars.example b/examples/fmc_prefilter_policy/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_prefilter_policy/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_range_objects/terraform.tfvars.example b/examples/fmc_range_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_range_objects/terraform.tfvars.example +++ b/examples/fmc_range_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_security_zone/terraform.tfvars.example b/examples/fmc_security_zone/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_security_zone/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_security_zones/terraform.tfvars.example b/examples/fmc_security_zones/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_security_zones/terraform.tfvars.example +++ b/examples/fmc_security_zones/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_sgt_objects/terraform.tfvars.example b/examples/fmc_sgt_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_sgt_objects/terraform.tfvars.example +++ b/examples/fmc_sgt_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_smart_license/terraform.tfvars.example b/examples/fmc_smart_license/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_smart_license/terraform.tfvars.example +++ b/examples/fmc_smart_license/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_standard_acl/terraform.tfvars.example b/examples/fmc_standard_acl/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_standard_acl/terraform.tfvars.example +++ b/examples/fmc_standard_acl/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_staticIPv4_route/terraform.tfvars.example b/examples/fmc_staticIPv4_route/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_staticIPv4_route/terraform.tfvars.example +++ b/examples/fmc_staticIPv4_route/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_syslog_alerts/terraform.tfvars.example b/examples/fmc_syslog_alerts/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_syslog_alerts/terraform.tfvars.example +++ b/examples/fmc_syslog_alerts/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_time_range_object/terraform.tfvars.example b/examples/fmc_time_range_object/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_time_range_object/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_url_object_groups/terraform.tfvars.example b/examples/fmc_url_object_groups/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_url_object_groups/terraform.tfvars.example +++ b/examples/fmc_url_object_groups/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_url_objects/terraform.tfvars.example b/examples/fmc_url_objects/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_url_objects/terraform.tfvars.example +++ b/examples/fmc_url_objects/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_vni/main.tf b/examples/fmc_vni/main.tf index 6335b9f1..022e5fe5 100644 --- a/examples/fmc_vni/main.tf +++ b/examples/fmc_vni/main.tf @@ -1,3 +1,18 @@ +terraform { + required_providers { + fmc = { + source = "CiscoDevNet/fmc" + # version = "0.1.1" + } + } +} + +provider "fmc" { + fmc_username = var.fmc_username + fmc_password = var.fmc_password + fmc_host = var.fmc_host + fmc_insecure_skip_verify = var.fmc_insecure_skip_verify +} data "fmc_devices" "device" { name = "10.100.0.218" diff --git a/examples/fmc_vni/provider.tf b/examples/fmc_vni/provider.tf deleted file mode 100644 index 8304067a..00000000 --- a/examples/fmc_vni/provider.tf +++ /dev/null @@ -1,15 +0,0 @@ -terraform { - required_providers { - fmc = { - source = "CiscoDevNet/fmc" - # version = "0.1.1" - } - } -} - -provider "fmc" { - fmc_username = var.fmc_username - fmc_password = var.fmc_password - fmc_host = var.fmc_host - fmc_insecure_skip_verify = var.fmc_insecure_skip_verify -} diff --git a/examples/fmc_vni/terraform.tfvars.example b/examples/fmc_vni/terraform.tfvars.example new file mode 100644 index 00000000..06bae73f --- /dev/null +++ b/examples/fmc_vni/terraform.tfvars.example @@ -0,0 +1,4 @@ +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/examples/fmc_vtep/terraform.tfvars.example b/examples/fmc_vtep/terraform.tfvars.example index cad6374c..06bae73f 100644 --- a/examples/fmc_vtep/terraform.tfvars.example +++ b/examples/fmc_vtep/terraform.tfvars.example @@ -1,4 +1,4 @@ -fmc_username = "api" -fmc_password = "CXsecurity!@34" -fmc_host = "10.106.107.228" -fmc_insecure_skip_verify = true \ No newline at end of file +fmc_username = "" +fmc_password = "" # Using env variable is recommended for sensitive variables +fmc_host = "1.2.3.4" +fmc_insecure_skip_verify = false diff --git a/fmc/data_source_fmc_device_cluster.go b/fmc/data_source_fmc_device_cluster.go new file mode 100644 index 00000000..cb4f1ba3 --- /dev/null +++ b/fmc/data_source_fmc_device_cluster.go @@ -0,0 +1,71 @@ +package fmc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceFmcDeviceCluster() *schema.Resource { + return &schema.Resource{ + Description: "Data source for FTD Device Cluster in FMC\n\n" + + "An example is shown below: \n" + + "```hcl\n" + + "data \"fmc_device_cluster\" \"cluster\" {\n" + + " name = \"ftd-cluster1\"\n" + + "}\n" + + "```", + ReadContext: dataSourceFmcDeviceClusterRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the cluster", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Type of this resource", + }, + }, + } +} + +func dataSourceFmcDeviceClusterRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + device, err := c.GetFmcDeviceClusterByName(ctx, d.Get("name").(string)) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get device cluster", + Detail: err.Error(), + }) + return diags + } + + d.SetId(device.ID) + + if err := d.Set("name", device.Name); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device cluster", + Detail: err.Error(), + }) + return diags + } + + if err := d.Set("type", device.Type); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device cluster", + Detail: err.Error(), + }) + return diags + } + return diags +} diff --git a/fmc/data_source_fmc_device_physicalinterfaces.go b/fmc/data_source_fmc_device_physicalinterfaces.go index 7ca5f971..9d41e3e5 100644 --- a/fmc/data_source_fmc_device_physicalinterfaces.go +++ b/fmc/data_source_fmc_device_physicalinterfaces.go @@ -14,6 +14,7 @@ func dataSourceFmcPhysicalInterface() *schema.Resource { "An example is shown below: \n" + "```hcl\n" + "data \"fmc_device_physical_interfaces\" \"test-phy-interfaces\" {\n" + + " device_id = \"\"\n" + " name = \"TEST-PHY\"\n" + "}\n" + "```", diff --git a/fmc/data_source_fmc_device_subinterfaces.go b/fmc/data_source_fmc_device_subinterfaces.go new file mode 100644 index 00000000..fd448bc8 --- /dev/null +++ b/fmc/data_source_fmc_device_subinterfaces.go @@ -0,0 +1,107 @@ +package fmc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceFmcSubInterfaces() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Sub-Interfaces in FMC\n\n" + + "An example is shown below: \n" + + "```hcl\n" + + "data \"fmc_device_subinterfaces\" \"sub_interfaces\" {\n" + + " device_id = \"\"\n" + + " subinterface_id = 1\n" + + "}\n" + + "```", + ReadContext: dataSourceSubInterfacesRead, + Schema: map[string]*schema.Schema{ + "device_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the device this resource needs", + }, + "subinterface_id": { + Type: schema.TypeInt, + Required: true, + Description: "ID of sub interface", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "SubInterface type", + }, + "if_name": { + Type: schema.TypeString, + Computed: true, + Description: "SubInterface logical name", + }, + "vlan_id": { + Type: schema.TypeInt, + Computed: true, + Description: "The Vlan ID needed for this resource", + }, + "mtu": { + Type: schema.TypeInt, + Computed: true, + Description: "SubInterface MTU", + }, + "mode": { + Type: schema.TypeString, + Computed: true, + Description: "The mode of this resource", + }, + "security_zone_id": { + Type: schema.TypeString, + Computed: true, + Description: "Security Zone ID", + }, + "ipv4_static_address": { + Type: schema.TypeString, + Computed: true, + Description: "IPv4 Static address", + }, + }, + } +} + +func dataSourceSubInterfacesRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + ifc, err := c.GetFmcSubInterfaceByName(ctx, d.Get("device_id").(string), d.Get("subinterface_id").(int)) + + d.Set("type", ifc.Type) + d.Set("if_name", ifc.Ifname) + d.Set("mtu", ifc.MTU) + d.Set("mode", ifc.Mode) + d.Set("security_zone_id", ifc.SecurityZone.ID) + d.Set("vlan_id", ifc.VlanID) + d.Set("ipv4_static_address", ifc.IPv4.Static.Address) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get subinterface", + Detail: err.Error(), + }) + return diags + } + + d.SetId(ifc.ID) + + if err := d.Set("subinterface_id", ifc.SubInterfaceID); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read subinterface", + Detail: err.Error(), + }) + return diags + } + + return diags +} diff --git a/fmc/data_source_fmc_port_group_objects.go b/fmc/data_source_fmc_port_group_objects.go new file mode 100644 index 00000000..9d8a12ef --- /dev/null +++ b/fmc/data_source_fmc_port_group_objects.go @@ -0,0 +1,59 @@ +package fmc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceFmcPortGroupObjects() *schema.Resource { + return &schema.Resource{ + Description: "Data source for Port Group Objects in FMC\n\n" + + "An example is shown below: \n" + + "```hcl\n" + + "data \"fmc_port_group_objects\" \"test\" {\n" + + " name = \"test-object-group\"\n" + + "}\n" + + "```", + + ReadContext: dataSourcePortGroupObjectsRead, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the port group object", + }, + }, + } +} + +func dataSourcePortGroupObjectsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + ifc, err := c.GetFmcPortGroupObjectByName(ctx, d.Get("name").(string)) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get port group object", + Detail: err.Error(), + }) + return diags + } + + d.SetId(ifc.ID) + + if err := d.Set("name", ifc.Name); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get port group object", + Detail: err.Error(), + }) + return diags + } + + return diags +} diff --git a/fmc/fmc_device.go b/fmc/fmc_device.go index 68f70407..bf38077c 100644 --- a/fmc/fmc_device.go +++ b/fmc/fmc_device.go @@ -6,8 +6,8 @@ import ( "encoding/json" "fmt" "net/http" - "time" "strings" + "time" ) type AccessPolicyItem struct { @@ -27,13 +27,13 @@ type getDeviceID struct { type FmcSpecific struct { Tags struct { } `json:"tags"` - TagKeys []any `json:"tagKeys"` - TagValues []any `json:"tagValues"` - UID string `json:"uid"` - Name string `json:"name"` - Namespace string `json:"namespace"` - Type string `json:"type"` - Version int `json:"version"` + TagKeys []any `json:"tagKeys"` + TagValues []any `json:"tagValues"` + UID string `json:"uid"` + Name string `json:"name"` + Namespace string `json:"namespace"` + Type string `json:"type"` + Version int `json:"version"` } type DevicesResponse struct { Links struct { @@ -45,12 +45,12 @@ type DevicesResponse struct { Links struct { Self string `json:"self"` } `json:"links"` - Name string `json:"name"` - HostName string `json:"hostName"` - NatID string `json:"natID,omitempty"` - RegKey string `json:"regKey,omitempty"` - AccessPolicy *AccessPolicyItem `json:"accessPolicy"` - PerformanceTier string `json:"performanceTier,omitempty"` + Name string `json:"name"` + HostName string `json:"hostName"` + NatID string `json:"natID,omitempty"` + RegKey string `json:"regKey,omitempty"` + AccessPolicy *AccessPolicyItem `json:"accessPolicy"` + PerformanceTier string `json:"performanceTier,omitempty"` } `json:"items"` Paging struct { Offset int `json:"offset"` @@ -65,55 +65,88 @@ type DeviceResponse struct { Self string `json:"self"` Parent string `json:"parent"` } `json:"links"` - Type string `json:"type"` - Description string `json:"description"` - ID string `json:"id"` - Name string `json:"name"` - HostName string `json:"hostName"` - RegKey string `json:"regKey,omitempty"` - NatID string `json:"natID,omitempty"` - PerformanceTier string `json:"performanceTier,omitempty"` - AccessPolicy AccessPolicyItem `json:"accessPolicy"` + Type string `json:"type"` + Description string `json:"description"` + ID string `json:"id"` + Name string `json:"name"` + HostName string `json:"hostName"` + RegKey string `json:"regKey,omitempty"` + NatID string `json:"natID,omitempty"` + PerformanceTier string `json:"performanceTier,omitempty"` + AccessPolicy AccessPolicyItem `json:"accessPolicy"` } type Device struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - HostName string `json:"hostName"` - NatID string `json:"natID,omitempty"` - RegKey string `json:"regKey,omitempty"` - AccessPolicy *AccessPolicyItem `json:"accessPolicy"` - PerformanceTier string `json:"performanceTier,omitempty"` - LicenseCaps []string `json:"license_caps,omitempty"` + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + HostName string `json:"hostName"` + NatID string `json:"natID,omitempty"` + RegKey string `json:"regKey,omitempty"` + AccessPolicy *AccessPolicyItem `json:"accessPolicy"` + PerformanceTier string `json:"performanceTier,omitempty"` + LicenseCaps []string `json:"license_caps,omitempty"` +} + +type DeviceBulk struct { + Devices []Device `json:"devices"` +} + +type DeviceBulkResponse struct { + DeviceResponses []DeviceResponse `json:"devicesresponses"` } func (v *Client) GetFmcDeviceByName(ctx context.Context, name string) (*Device, error) { - url := fmt.Sprintf("%s/devices/devicerecords?filter=name:%s", v.domainBaseURL, name) - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) - if err != nil { - return nil, fmt.Errorf("getting device by name: %s - %s", url, err.Error()) - } - Log.debug(req, "request") - devices := &DevicesResponse{} - err = v.DoRequest(req, devices, http.StatusOK) - if err != nil { - return nil, fmt.Errorf("getting device by name: %s - %s", url, err.Error()) - } - - for _, device := range devices.Items { - if device.Name == name { - Log.debug(device, "response") - return &Device{ - ID: device.ID, - Name: device.Name, - Type: device.Type, - NatID: device.NatID, - RegKey: device.RegKey, - }, nil - } - } - return nil, fmt.Errorf("no device found with name %s", name) + url := fmt.Sprintf("%s/devices/devicerecords?filter=name:%s", v.domainBaseURL, name) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting device by name: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + devices := &DevicesResponse{} + err = v.DoRequest(req, devices, http.StatusOK) + if err != nil { + return nil, fmt.Errorf("getting device by name: %s - %s", url, err.Error()) + } + + for _, device := range devices.Items { + if device.Name == name { + Log.debug(device, "response") + return &Device{ + ID: device.ID, + Name: device.Name, + Type: device.Type, + NatID: device.NatID, + RegKey: device.RegKey, + }, nil + } + } + return nil, fmt.Errorf("no device found with name %s", name) +} + +func (v *Client) CreateFmcDeviceBulk(ctx context.Context, devices *DeviceBulk) (*DeviceBulkResponse, error) { + url := fmt.Sprintf("%s/devices/devicerecords?bulk=true", v.domainBaseURL) + body, err := json.Marshal(devices.Devices) + if err != nil { + return nil, fmt.Errorf("adding device1: %s - %s", url, err.Error()) + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("adding device2: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + item := &DeviceBulkResponse{} + err = v.DoRequest(req, item, http.StatusAccepted) + if err != nil { + return nil, fmt.Errorf("adding device3: %s - %s", url, err.Error()) + } + + time.Sleep(300 * time.Second) + // time.Sleep(30 * time.Second) + Log.debug(item, "response") + Log.line() + return item, nil } func (v *Client) CreateFmcDevice(ctx context.Context, device *Device) (*DeviceResponse, error) { @@ -138,7 +171,9 @@ func (v *Client) CreateFmcDevice(ctx context.Context, device *Device) (*DeviceRe if err != nil { return nil, fmt.Errorf("adding device3: %s - %s", url, err.Error()) } + time.Sleep(300 * time.Second) + // time.Sleep(30 * time.Second) Log.debug(item, "response") Log.line() return item, nil @@ -182,107 +217,119 @@ func (v *Client) UpdateFmcDevice(ctx context.Context, id string, device *Device) return item, nil } -func (v *Client) DeleteFmcCDODevice(ctx context.Context, name string,cdoHost string, cdoRegion string) error { - // fetch ftd id - url := fmt.Sprintf("https://edge.%s.cdo.cisco.com/api/public",cdoRegion) - - method := "POST" - payload_temp := fmt.Sprintf("{\"query\":\"query {\\n devices(name: \\\"%s\\\") {\\n items {\\n name\\n uid\\n }\\n }\\n}\",\"variables\":{}}", name) - payload2 := strings.NewReader("{\"query\":\"query {\\n devices(name: \\\"FMC\\\") {\\n items {\\n name\\n uid\\n }\\n }\\n}\",\"variables\":{}}") - - payload := strings.NewReader(payload_temp) - - req,err := http.NewRequestWithContext( ctx,method, url, payload) - if err != nil { - fmt.Println(err) - return err - } - - res := &getDeviceID{} - err = v.DoRequest(req, res, http.StatusOK) - - if err != nil { - fmt.Println(err) - return err - } - - device_uid := res.Data.Devices.Items[0].UID - fmt.Println(device_uid) - - // fetch fmc id - - req2,err := http.NewRequestWithContext( ctx,method, url, payload2) - if err != nil { - fmt.Println(err) - return err - } - - res2 := &getDeviceID{} - err = v.DoRequest(req2, res2, http.StatusOK) - if err != nil { - fmt.Println(err) - return err - } - - fmc_uid := res2.Data.Devices.Items[0].UID - fmt.Println(fmc_uid) - // fetch specific fmc id - - url2 := fmt.Sprintf("https://%s/aegis/rest/v1/device/%s/specific-device", cdoHost,fmc_uid) - fmcReq, err := http.NewRequestWithContext(ctx,"GET", url2, nil) - if err != nil { - fmt.Println(err) - return err - } - fmc_res := &FmcSpecific{} - err = v.DoRequest(fmcReq, fmc_res, http.StatusOK) - - if err != nil { - fmt.Println(err) - return err - } - - fmc_specific_id := fmc_res.UID - fmt.Println(fmc_specific_id) - - // delete device - - url3 := fmt.Sprintf("https://%s/aegis/rest/v1/services/fmc/appliance/%s", cdoHost,fmc_specific_id) - payload_temp2 := fmt.Sprintf("{\"queueTriggerState\": \"PENDING_DELETE_FTDC\",\"stateMachineContext\": {\"ftdCDeviceIDs\": \"%s\"}}", device_uid) - payload3 := strings.NewReader(payload_temp2) - - del, err := http.NewRequestWithContext(ctx, "PUT", url3, payload3) - - if err != nil { - fmt.Println(err) - return err - } - err = v.DoRequest(del, nil, http.StatusOK) - if err != nil { - fmt.Println(err) - return err - } - return err +func (v *Client) DeleteFmcCDODevice(ctx context.Context, name string, cdoHost string, cdoRegion string) error { + // fetch ftd id + url := fmt.Sprintf("https://edge.%s.cdo.cisco.com/api/public", cdoRegion) + + method := "POST" + payload_temp := fmt.Sprintf("{\"query\":\"query {\\n devices(name: \\\"%s\\\") {\\n items {\\n name\\n uid\\n }\\n }\\n}\",\"variables\":{}}", name) + payload2 := strings.NewReader("{\"query\":\"query {\\n devices(name: \\\"FMC\\\") {\\n items {\\n name\\n uid\\n }\\n }\\n}\",\"variables\":{}}") + + payload := strings.NewReader(payload_temp) + + req, err := http.NewRequestWithContext(ctx, method, url, payload) + if err != nil { + fmt.Println(err) + return err + } + + res := &getDeviceID{} + err = v.DoRequest(req, res, http.StatusOK) + + if err != nil { + fmt.Println(err) + return err + } + + device_uid := res.Data.Devices.Items[0].UID + fmt.Println(device_uid) + + // fetch fmc id + + req2, err := http.NewRequestWithContext(ctx, method, url, payload2) + if err != nil { + fmt.Println(err) + return err + } + + res2 := &getDeviceID{} + err = v.DoRequest(req2, res2, http.StatusOK) + if err != nil { + fmt.Println(err) + return err + } + + fmc_uid := res2.Data.Devices.Items[0].UID + fmt.Println(fmc_uid) + // fetch specific fmc id + + url2 := fmt.Sprintf("https://%s/aegis/rest/v1/device/%s/specific-device", cdoHost, fmc_uid) + fmcReq, err := http.NewRequestWithContext(ctx, "GET", url2, nil) + if err != nil { + fmt.Println(err) + return err + } + fmc_res := &FmcSpecific{} + err = v.DoRequest(fmcReq, fmc_res, http.StatusOK) + + if err != nil { + fmt.Println(err) + return err + } + + fmc_specific_id := fmc_res.UID + fmt.Println(fmc_specific_id) + + // delete device + + url3 := fmt.Sprintf("https://%s/aegis/rest/v1/services/fmc/appliance/%s", cdoHost, fmc_specific_id) + payload_temp2 := fmt.Sprintf("{\"queueTriggerState\": \"PENDING_DELETE_FTDC\",\"stateMachineContext\": {\"ftdCDeviceIDs\": \"%s\"}}", device_uid) + payload3 := strings.NewReader(payload_temp2) + + del, err := http.NewRequestWithContext(ctx, "PUT", url3, payload3) + + if err != nil { + fmt.Println(err) + return err + } + err = v.DoRequest(del, nil, http.StatusOK) + if err != nil { + fmt.Println(err) + return err + } + return err } -func (v *Client) DeleteFmcDevice(ctx context.Context, m interface{},id string, name string,cdoHost string, cdoRegion string) error { - var err error - if v.is_cdfmc { - c := m.(*Client) - err := c.DeleteFmcCDODevice(ctx, name, cdoHost, cdoRegion) - if err != nil { - return err - } - } else { - url := fmt.Sprintf("%s/devices/devicerecords/%s", v.domainBaseURL, id) - req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) - if err != nil { - return fmt.Errorf("deleting Device: %s - %s", url, err.Error()) - } - Log.debug(req, "request") - err = v.DoRequest(req, nil, http.StatusOK) - Log.line() - return err - } - return err -} \ No newline at end of file +func (v *Client) DeleteFmcDevice(ctx context.Context, m interface{}, id string, name string, cdoHost string, cdoRegion string) error { + var err error + if v.is_cdfmc { + c := m.(*Client) + err := c.DeleteFmcCDODevice(ctx, name, cdoHost, cdoRegion) + if err != nil { + return err + } + } else { + url := fmt.Sprintf("%s/devices/devicerecords/%s", v.domainBaseURL, id) + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return fmt.Errorf("deleting Device: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + err = v.DoRequest(req, nil, http.StatusOK) + Log.line() + return err + } + return err +} + +func (v *Client) DeleteFmcDeviceBulk(ctx context.Context, ids string) error { + url := fmt.Sprintf("%s/devices/devicerecords?filter:ids=%s", v.domainBaseURL, ids) + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return fmt.Errorf("deleting Device: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + err = v.DoRequest(req, nil, http.StatusOK) + Log.line() + return err +} diff --git a/fmc/fmc_device_cluster.go b/fmc/fmc_device_cluster.go new file mode 100644 index 00000000..c8fa4e1a --- /dev/null +++ b/fmc/fmc_device_cluster.go @@ -0,0 +1,177 @@ +package fmc + +import ( + "context" + "fmt" + "net/http" + "bytes" + "encoding/json" + "time" +) +type ClustersResponse struct { + Links struct { + Self string `json:"self"` + } `json:"links"` + Items []struct { + ID string `json:"id"` + Type string `json:"type"` + Links struct { + Self string `json:"self"` + } `json:"links"` + Name string `json:"name"` + ControlDevice ControlDevice `json:"controlDevice"` + CommonBootstrap CommonBootstrap `json:"commonBootstrap"` + DataDevices []ControlDevice `json:"dataDevices"` + } `json:"items"` + Paging struct { + Offset int `json:"offset"` + Limit int `json:"limit"` + Count int `json:"count"` + Pages int `json:"pages"` + } `json:"paging"` +} + +type ClusterResponse struct { + Links struct { + Self string `json:"self"` + Parent string `json:"parent"` + } `json:"links"` + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + ControlDevice ControlDevice `json:"controlDevice"` + CommonBootstrap CommonBootstrap `json:"commonBootstrap"` + DataDevices []ControlDevice `json:"dataDevices"` +} +type Cluster struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Name string `json:"name,omitempty"` + ControlDevice ControlDevice `json:"controlDevice,omitempty"` + CommonBootstrap CommonBootstrap `json:"commonBootstrap,omitempty"` + DataDevices []ControlDevice `json:"dataDevices,omitempty"` + Action string `json:"action,omitempty"` +} +type DataDevices struct { + DataDevices []ControlDevice `json:"dataDevices"` +} + +type ClusterNodeBootstrap struct { + CclIP string `json:"cclIp,omitempty"` + Priority int `json:"priority,omitempty"` +} + +type DeviceDetails struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` +} + +type ControlDevice struct { + ClusterNode *ClusterNodeBootstrap `json:"clusterNodeBootstrap,omitempty"` + DeviceDetail *DeviceDetails `json:"deviceDetails,omitempty"` +} + +type CommonBootstrap struct { + CclInterface *DeviceDetails `json:"cclInterface,omitempty"` + CclNetwork string `json:"cclNetwork,omitempty"` + VniNetwork string `json:"vniNetwork,omitempty"` +} + +func (v *Client) GetFmcDeviceClusterByName(ctx context.Context, name string) (*Cluster, error) { + url := fmt.Sprintf("%s/deviceclusters/ftddevicecluster?offset=0&limit=25&expanded=true", v.domainBaseURL) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting cluster by name: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + clusters := &ClustersResponse{} + err = v.DoRequest(req, clusters, http.StatusOK) + if err != nil { + return nil, fmt.Errorf("getting cluster by name: %s - %s", url, err.Error()) + } + + for _, cluster := range clusters.Items { + if cluster.Name == name { + Log.debug(cluster, "response") + return &Cluster{ + ID: cluster.ID, + Name: cluster.Name, + Type: cluster.Type, + }, nil + } + } + return nil, fmt.Errorf("no device found with name %s", name) +} + +func (v *Client) CreateFmcDeviceCluster(ctx context.Context, cluster *Cluster) (*ClusterResponse, error) { + url := fmt.Sprintf("%s/deviceclusters/ftddevicecluster", v.domainBaseURL) + body, err := json.Marshal(&cluster) + if err != nil { + return nil, fmt.Errorf("creating cluster: %s - %s", url, err.Error()) + } + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("creating cluster: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + item := &ClusterResponse{} + err = v.DoRequest(req, item, http.StatusAccepted) + if err != nil { + return nil, fmt.Errorf("creating cluster3: %s - %s", url, err.Error()) + } + time.Sleep(180 * time.Second) + Log.debug(item, "response") + Log.line() + return item, nil +} + +func (v *Client) GetFmcDeviceCluster(ctx context.Context, id string) (*ClusterResponse, error) { + url := fmt.Sprintf("%s/deviceclusters/ftddevicecluster/%s", v.domainBaseURL, id) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting cluster: %s - %s - id: %s", url, err.Error(), id) + } + Log.debug(req, "request") + item := &ClusterResponse{} + err = v.DoRequest(req, item, http.StatusOK) + if err != nil { + return nil, fmt.Errorf("getting cluster: %s - %s - id: %s", url, err.Error(), id) + } + Log.debug(item, "response") + Log.line() + return item, nil +} + +func (v *Client) UpdateFmcDeviceCluster(ctx context.Context, id string, cluster *Cluster) (*ClusterResponse, error) { + url := fmt.Sprintf("%s/deviceclusters/ftddevicecluster/%s", v.domainBaseURL, id) + body, err := json.Marshal(&cluster) + if err != nil { + return nil, fmt.Errorf("updating cluster: %s - %s", url, err.Error()) + } + req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("updating cluster: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + item := &ClusterResponse{} + err = v.DoRequest(req, item, http.StatusOK) + if err != nil { + return nil, fmt.Errorf("Updating cluster: %s - %s", url, err.Error()) + } + Log.debug(item, "response") + Log.line() + return item, nil +} + +func (v *Client) DeleteFmcDeviceCluster(ctx context.Context, m interface{},id string) error { + url := fmt.Sprintf("%s/deviceclusters/ftddevicecluster/%s", v.domainBaseURL, id) + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return fmt.Errorf("deleting cluster: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + err = v.DoRequest(req, nil, http.StatusOK) + Log.line() + return err +} \ No newline at end of file diff --git a/fmc/fmc_device_physicalinterfaces.go b/fmc/fmc_device_physicalinterfaces.go index 68f6654f..6b60c06c 100644 --- a/fmc/fmc_device_physicalinterfaces.go +++ b/fmc/fmc_device_physicalinterfaces.go @@ -17,6 +17,7 @@ type IPv6Address struct { } type IPv6 struct { + EnableIPv6 bool `json:"enableIPV6,omitempty"` Addresses []IPv6Address `json:"addresses,omitempty"` } @@ -38,8 +39,8 @@ type IPv4 struct { } type PhysicalInterfaceSecurityZone struct { - ID string `json:"id"` - Type string `json:"type"` + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` } type PhysicalInterfaceResponse struct { diff --git a/fmc/fmc_device_subinterfaces.go b/fmc/fmc_device_subinterfaces.go new file mode 100644 index 00000000..b4458b14 --- /dev/null +++ b/fmc/fmc_device_subinterfaces.go @@ -0,0 +1,179 @@ +package fmc + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" +) + +type SubInterface struct { + Ifname string `json:"ifname,omitempty"` + ID string `json:"id,omitempty"` + Type string `json:"type"` + Mode string `json:"mode,omitempty"` + SubInterfaceID int `json:"subIntfId"` + VlanID int `json:"vlanId,omitempty"` + MTU int `json:"MTU,omitempty"` + Enabled bool `json:"enabled,omitempty"` + MgmntOnly bool `json:"managementOnly,omitempty"` + Priority int `json:"priority,omitempty"` + IPv4 IPv4 `json:"ipv4,omitempty"` + IPv6 IPv6 `json:"ipv6,omitempty"` + SecurityZone PhysicalInterfaceSecurityZone `json:"securityZone,omitempty"` + Description string `json:"description,omitempty"` + Name string `json:"name"` +} + +type SubInterfaceResponse struct { + Links struct { + Self string `json:"self"` + Parent string `json:"parent"` + } `json:"links"` + Type string `json:"type"` + Ifname string `json:"ifname"` + SubInterfaceID int `json:"subIntfId"` + Enabled bool `json:"enabled"` + MgmntOnly bool `json:"managementOnly,omitempty"` + Priority int `json:"priority,omitempty"` + VlanID int `json:"vlanId,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + MTU int `json:"MTU,omitempty"` + SecurityZone PhysicalInterfaceSecurityZone `json:"securityZone"` + Mode string `json:"mode"` + IPv4 IPv4 `json:"ipv4,omitempty"` +} + +type SubInterfacesResponse struct { + Links struct { + Self string `json:"self"` + } `json:"links"` + Items []struct { + ID string `json:"id"` + Ifname string `json:"ifname"` + SubInterfaceID int `json:"subIntfId"` + MTU int `json:"MTU,omitempty"` + SecurityZone PhysicalInterfaceSecurityZone `json:"securityZone"` + IPv4 IPv4 `json:"ipv4,omitempty"` + VlanID int `json:"vlanId,omitempty"` + Type string `json:"type"` + Links struct { + Self string `json:"self"` + } `json:"links"` + Name string `json:"name"` + } `json:"items"` + Paging struct { + Offset int `json:"offset"` + Limit int `json:"limit"` + Count int `json:"count"` + Pages int `json:"pages"` + } `json:"paging"` +} + +func (v *Client) GetFmcSubInterfaceByName(ctx context.Context, deviceID string, subinterface_id int) (*SubInterfaceResponse, error) { + url := fmt.Sprintf("%s/devices/devicerecords/%s/subinterfaces?expanded=true", v.domainBaseURL, deviceID) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting sub interfaces: %s - %s", url, err.Error()) + } + subInterfaces := &SubInterfacesResponse{} + err = v.DoRequest(req, subInterfaces, http.StatusOK) + + if err != nil { + return nil, fmt.Errorf("getting sub interfaces: %s - %s", url, err.Error()) + } + + for _, SubInterface := range subInterfaces.Items { + if SubInterface.SubInterfaceID == subinterface_id { + return &SubInterfaceResponse{ + ID: SubInterface.ID, + Name: SubInterface.Name, + SubInterfaceID: SubInterface.SubInterfaceID, + Type: SubInterface.Type, + Ifname: SubInterface.Ifname, + MTU: SubInterface.MTU, + SecurityZone: SubInterface.SecurityZone, + IPv4: SubInterface.IPv4, + VlanID: SubInterface.VlanID, + }, nil + } + } + + return nil, fmt.Errorf("no SubInterface found with id %d", subinterface_id) +} +func (v *Client) GetFmcSubInterface(ctx context.Context, deviceID string, id string) (*SubInterfaceResponse, error) { + url := fmt.Sprintf("%s/devices/devicerecords/%s/subinterfaces/%s", v.domainBaseURL, deviceID, id) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting sub interfaces: %s - %s", url, err.Error()) + } + subInterfaces := &SubInterfaceResponse{} + err = v.DoRequest(req, subInterfaces, http.StatusOK) + + if err != nil { + return nil, fmt.Errorf("getting sub interfaces: %s - %s", url, err.Error()) + } + + if subInterfaces != nil { + return &SubInterfaceResponse{ + ID: subInterfaces.ID, + Name: subInterfaces.Name, + Enabled: subInterfaces.Enabled, + Ifname: subInterfaces.Ifname, + Type: subInterfaces.Type, + MTU: subInterfaces.MTU, + Mode: subInterfaces.Mode, + SecurityZone: subInterfaces.SecurityZone, + }, nil + } + + return nil, fmt.Errorf("no Interface found with physicalInterfaceId %s", id) +} +func (v *Client) CreateFmcSubInterface(ctx context.Context, deviceID string, object *SubInterface) (*SubInterfaceResponse, error) { + url := fmt.Sprintf("%s/devices/devicerecords/%s/subinterfaces", v.domainBaseURL, deviceID) + body, err := json.Marshal(&object) + if err != nil { + return nil, fmt.Errorf("creating sub interfaces: %s - %s", url, err.Error()) + } + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("creating sub interfaces: %s - %s", url, err.Error()) + } + item := &SubInterfaceResponse{} + err = v.DoRequest(req, item, http.StatusCreated) + if err != nil { + return nil, fmt.Errorf("creating sub interfaces: %s - %s", url, err.Error()) + } + return item, nil +} +func (v *Client) UpdateFmcSubInterface(ctx context.Context, deviceID string, id string, object *SubInterface) (*SubInterfaceResponse, error) { + url := fmt.Sprintf("%s/devices/devicerecords/%s/subinterfaces/%s", v.domainBaseURL, deviceID, id) + body, err := json.Marshal(&object) + if err != nil { + return nil, fmt.Errorf("updating subinterfaces: %s - %s", url, err.Error()) + } + req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("updating subinterfaces: %s - %s", url, err.Error()) + } + item := &SubInterfaceResponse{} + err = v.DoRequest(req, item, http.StatusOK) + if err != nil { + return nil, fmt.Errorf("getting subinterfaces: %s - %s", url, err.Error()) + } + return item, nil +} + +func (v *Client) DeleteFmcSubInterface(ctx context.Context, deviceID string, id string) error{ + url := fmt.Sprintf("%s/devices/devicerecords/%s/subinterfaces/%s", v.domainBaseURL, deviceID, id) + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return fmt.Errorf("deleting subinterface: %s - %s", url, err.Error()) + } + err = v.DoRequest(req, nil, http.StatusOK) + + return err +} + diff --git a/fmc/fmc_helper_functions.go b/fmc/fmc_helper_functions.go index 26663289..1abc1733 100644 --- a/fmc/fmc_helper_functions.go +++ b/fmc/fmc_helper_functions.go @@ -1,8 +1,10 @@ package fmc import ( + "encoding/hex" "fmt" "reflect" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" ) @@ -63,3 +65,77 @@ func ToMap(in interface{}, tag string) (map[string]interface{}, error) { } return out, nil } + +func ConvertStructToMap(obj interface{}) []map[string]interface{} { + result := make([]map[string]interface{}, 1) + result[0] = make(map[string]interface{}) + + // Iterate through the fields of the object + val := reflect.ValueOf(obj) + typ := reflect.TypeOf(obj) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + fieldName := strings.ToLower(field.Name) // Convert field name to lowercase + + // Check if the field value is a struct + if fieldValue.Kind() == reflect.Struct { + // Recursively convert the struct to a map + result[0][fieldName] = ConvertStructToMap(fieldValue.Interface()) + } else { + // Convert field value to lowercase if it is a string + if fieldValue.Kind() == reflect.String { + strValue := fieldValue.Interface().(string) + hexBytes, err := hex.DecodeString(strValue) + if err == nil { + result[0][fieldName] = hex.EncodeToString(hexBytes) + } else { + result[0][fieldName] = strings.ToLower(strValue) + } + } else { + // Otherwise, use the field value as is + result[0][fieldName] = fieldValue.Interface() + } + } + } + + return result +} + +func isEqual(obj1, obj2 interface{}, keysToCompare ...string) bool { + if reflect.TypeOf(obj1).Kind() == reflect.Map { + map1 := reflect.ValueOf(obj1) + map2 := reflect.ValueOf(obj2) + + // Check if the specified keys are present and equal + for _, key := range keysToCompare { + val1 := map1.MapIndex(reflect.ValueOf(key)) + val2 := map2.MapIndex(reflect.ValueOf(key)) + + if !val1.IsValid() || !val2.IsValid() { + return false + } + + // If the values have interfaces, access the id fields for comparison + if val1.Elem().Index(0).Kind() == reflect.Map && val2.Elem().Index(0).Kind() == reflect.Map { + id1 := val1.Elem().Index(0).MapIndex(reflect.ValueOf("id")).Interface() + id2 := val2.Elem().Index(0).MapIndex(reflect.ValueOf("id")).Interface() + id1 = strings.ToLower(id1.(string)) + id2 = strings.ToLower(id2.(string)) + + if id1 != id2 { + return false + } + } else { + // Use reflect.DeepEqual for non-interface values + if !reflect.DeepEqual(val1.Interface(), val2.Interface()) { + return false + } + } + } + + return true + } + + return false +} diff --git a/fmc/fmc_network_object.go b/fmc/fmc_network_object.go index 11ef496e..b0f2c33a 100644 --- a/fmc/fmc_network_object.go +++ b/fmc/fmc_network_object.go @@ -25,6 +25,10 @@ type NetworkObject struct { Type string `json:"type"` } +type NetworkObjects struct { + Objects []NetworkObject `json:"objects"` +} + type NetworkObjectResponse struct { Type string `json:"type"` Value string `json:"value"` @@ -72,6 +76,27 @@ func (v *Client) GetFmcNetworkObjectByNameOrValue(ctx context.Context, nameOrVal // /fmc_config/v1/domain/DomainUUID/object/networks?bulk=true ( Bulk POST operation on network objects. ) +func (v *Client) CreateFmcNetworkObjectBulk(ctx context.Context, object *NetworkObjects) (*NetworkObjectsResponse, error) { + url := fmt.Sprintf("%s/object/networks?bulk=true", v.domainBaseURL) + body, err := json.Marshal(object.Objects) + if err != nil { + return nil, fmt.Errorf("creating network objects: %s - %s", url, err.Error()) + } + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(body)) + if err != nil { + return nil, fmt.Errorf("creating network objects: %s - %s", url, err.Error()) + } + Log.debug(req, "request") + item := &NetworkObjectsResponse{} + err = v.DoRequest(req, item, http.StatusCreated) + if err != nil { + return nil, fmt.Errorf("getting network objects: %s - %s", url, err.Error()) + } + Log.debug(item, "response") + Log.line() + return item, nil +} + func (v *Client) CreateFmcNetworkObject(ctx context.Context, object *NetworkObject) (*NetworkObjectResponse, error) { url := fmt.Sprintf("%s/object/networks", v.domainBaseURL) body, err := json.Marshal(&object) diff --git a/fmc/fmc_port_group_object.go b/fmc/fmc_port_group_object.go index 5cd2f07a..2cb3c483 100644 --- a/fmc/fmc_port_group_object.go +++ b/fmc/fmc_port_group_object.go @@ -41,6 +41,10 @@ type PortGroupObjectResponse struct { ID string `json:"id"` } +type PortGroupObjectsResponse struct { + Items []PortGroupObjectResponse `json:"items"` +} + // /fmc_config/v1/domain/DomainUUID/object/portobjectgroups?bulk=true ( Bulk POST operation on port group objects. ) func (v *Client) CreateFmcPortGroupObject(ctx context.Context, object *PortGroupObject) (*PortGroupObjectResponse, error) { @@ -81,6 +85,32 @@ func (v *Client) GetFmcPortGroupObject(ctx context.Context, id string) (*PortGro return item, nil } +func (v *Client) GetFmcPortGroupObjectByName(ctx context.Context, name string) (*PortGroupObjectResponse, error) { + url := fmt.Sprintf("%s/object/portobjectgroups", v.domainBaseURL) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("getting port group object: %s - %s", url, err.Error()) + } + portGroupObjects := &PortGroupObjectsResponse{} + err = v.DoRequest(req, portGroupObjects, http.StatusOK) + + if err != nil { + return nil, fmt.Errorf("getting port group object: %s - %s", url, err.Error()) + } + + for _, PortGroupObjects := range portGroupObjects.Items { + if PortGroupObjects.Name == name { + return &PortGroupObjectResponse{ + ID: PortGroupObjects.ID, + Name: PortGroupObjects.Name, + Type: PortGroupObjects.Type, + }, nil + } + } + + return nil, fmt.Errorf("no port group object found with name %s", name) +} + func (v *Client) UpdateFmcPortGroupObject(ctx context.Context, id string, object *PortGroupObjectUpdateInput) (*PortGroupObjectResponse, error) { url := fmt.Sprintf("%s/object/portobjectgroups/%s", v.domainBaseURL, id) body, err := json.Marshal(&object) diff --git a/fmc/fmc_provider.go b/fmc/fmc_provider.go index 6a0eb6c5..7faf0a25 100644 --- a/fmc/fmc_provider.go +++ b/fmc/fmc_provider.go @@ -123,6 +123,10 @@ func Provider() *schema.Provider { "fmc_standard_acl": resourceFmcStandardAcl(), "fmc_network_analysis_policy": resourceFmcNetworkAnalysisPolicy(), "fmc_smart_license": resourceFmcSmartLicense(), + "fmc_device_subinterfaces": resourceFmcSubInterface(), + "fmc_network_objects_bulk": resourceFmcNetworkObjectsBulk(), + "fmc_devices_bulk": resourceFmcDevicesBulk(), + "fmc_device_cluster": resourceFmcDeviceCluster(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -149,6 +153,9 @@ func Provider() *schema.Provider { "fmc_network_analysis_policy": dataSourceFmcNetworkAnalysisPolicy(), "fmc_device_vtep_policies": dataSourceFmcVTEPPolicies(), "fmc_smart_license": dataSourceFmcSmartLicense(), + "fmc_device_subinterfaces": dataSourceFmcSubInterfaces(), + "fmc_port_group_objects": dataSourceFmcPortGroupObjects(), + "fmc_device_cluster": dataSourceFmcDeviceCluster(), }, ConfigureContextFunc: providerConfigure, } diff --git a/fmc/resource_fmc_device.go b/fmc/resource_fmc_device.go index 322a0ba9..afafb751 100644 --- a/fmc/resource_fmc_device.go +++ b/fmc/resource_fmc_device.go @@ -20,7 +20,6 @@ func resourceFmcDevices() *schema.Resource { " name = \"ftd\"\n" + " hostname = \"\"\n" + " regkey = \"\"\n" + - " metric_value = 22\n" + " license_caps = [\n" + " \"MALWARE\"\n" + " ]\n" + @@ -276,7 +275,7 @@ func resourceFmcDeviceDelete(ctx context.Context, d *schema.ResourceData, m inte id := d.Id() - err := c.DeleteFmcDevice(ctx, m, id,d.Get("name").(string),d.Get("cdo_host").(string),d.Get("cdo_region").(string) ) + err := c.DeleteFmcDevice(ctx, m, id, d.Get("name").(string), d.Get("cdo_host").(string), d.Get("cdo_region").(string)) if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, diff --git a/fmc/resource_fmc_device_bulk.go b/fmc/resource_fmc_device_bulk.go new file mode 100644 index 00000000..1dd8bdd8 --- /dev/null +++ b/fmc/resource_fmc_device_bulk.go @@ -0,0 +1,520 @@ +package fmc + +import ( + "context" + "strconv" + "strings" + "time" + + // "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceFmcDevicesBulk() *schema.Resource { + return &schema.Resource{ + Description: "Resource for adding bulk devices in FMC\n" + + "\n" + + "## Example\n" + + "An example is shown below: \n" + + "```hcl\n" + + "resource \"fmc_devices_bulk\" \"devices\" {\n" + + " devices {\n" + + " name = \"ftd\"\n" + + " hostname = \"\"\n" + + " regkey = \"\"\n" + + " license_caps = [\n" + + " \"MALWARE\"\n" + + " ]\n" + + " access_policy {\n" + + " id = data.fmc_access_policies.access_policy.id\n" + + " }\n" + + " }\n" + + " devices {\n" + + " name = \"ftd2\"\n" + + " hostname = \"\"\n" + + " regkey = \"\"\n" + + " license_caps = [\n" + + " \"MALWARE\"\n" + + " ]\n" + + " access_policy {\n" + + " id = data.fmc_access_policies.access_policy.id\n" + + " }\n" + + " }\n" + + "}\n" + + "```\n" + + "**Note:** If creating multiple rules during a single `terraform apply`, remember to use `depends_on` to chain the rules so that terraform creates it in the same order that you intended.\n", + CreateContext: resourceFmcDeviceCreateBulk, + ReadContext: resourceFmcDeviceReadBulk, + UpdateContext: resourceFmcDeviceUpdateBulk, + DeleteContext: resourceFmcDeviceDeleteBulk, + Schema: map[string]*schema.Schema{ + "devices": { + Type: schema.TypeList, + Required: true, + MaxItems: 100, + MinItems: 1, + Description: "The list of network objects to create", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of this resource", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "The name of FTD", + }, + "hostname": { + Type: schema.TypeString, + Required: true, + Description: "The hostname of FTD", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of this resource", + }, + "regkey": { + Type: schema.TypeString, + Required: true, + Description: "Same regkey as entered in FTD", + }, + "nat_id": { + Type: schema.TypeString, + Optional: true, + Description: "NAT_ID is required, if configured in FTD ", + }, + "performance_tier": { + Type: schema.TypeString, + Optional: true, + Description: "Select the desired performace tier", + }, + "license_caps": { + Type: schema.TypeList, + Optional: true, + Description: "License caps for this resource", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "access_policy": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of this resource", + }, + "type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of this resource", + }, + }, + }, + Description: "access policy for this resource", + }, + }, + }, + }, + "id_mappings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of this resource", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of this resource", + }, + }, + }, + Description: "id array", + }, + }, + } +} + +func resourceFmcDeviceCreateBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + // Warning or errors can be collected in a slice type + // var diags diag.Diagnostics + var diags diag.Diagnostics + devices := d.Get("devices").([]interface{}) + Log.info(devices) + + obj := DeviceBulk{ + Devices: make([]Device, len(devices)), + } + + for ind, device := range devices { + device := device.(map[string]interface{}) + Log.info(device) + var accpolicy *AccessPolicyItem + dynamicObjects2 := []**AccessPolicyItem{ + &accpolicy, + } + + if inputEntries, ok := device["access_policy"]; ok { + entry := inputEntries.([]interface{})[0].(map[string]interface{}) + + *dynamicObjects2[0] = &AccessPolicyItem{ + ID: entry["id"].(string), + Type: entry["type"].(string), + } + } + + lcap := []string{} + for _, lic := range device["license_caps"].([]interface{}) { + lcap = append(lcap, lic.(string)) + } + + obj.Devices[ind] = Device{ + Name: device["name"].(string), + HostName: device["hostname"].(string), + Type: device_type, + RegKey: device["regkey"].(string), + NatID: device["nat_id"].(string), + PerformanceTier: device["performance_tier"].(string), + LicenseCaps: lcap, + AccessPolicy: accpolicy, + } + } + + _, err := c.CreateFmcDeviceBulk(ctx, &obj) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to add device", + Detail: err.Error(), + }) + return diags + } + + time.Sleep(300 * time.Second) + //ADD CODE TO GET ID + + for _, dev := range devices { + device, err := c.GetFmcDeviceByName(ctx, dev.(map[string]interface{})["name"].(string)) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get device", + Detail: err.Error(), + }) + return diags + } + + id_array_map = append(id_array_map, map[string]interface{}{ + "id": device.ID, + "name": device.Name, + }) + d.Set("id_mappings", id_array_map) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + //Code ENDED + return resourceFmcDeviceReadBulk(ctx, d, m) +} + +func resourceFmcDeviceReadBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + var all_obj []interface{} + for _, dev := range d.Get("id_mappings").([]interface{}) { + item, err := c.GetFmcDevice(ctx, dev.(map[string]interface{})["id"].(string)) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device", + Detail: err.Error(), + }) + return diags + } + + obj := []interface{}{ + map[string]interface{}{ + "name": item.Name, + "type": item.Type, + "id": item.ID, + "hostname": item.HostName, + "nat_id": item.NatID, + }, + } + all_obj = append(all_obj, obj[0]) + } + + if err := d.Set("devices", all_obj); err != nil { + return returnWithDiag(diags, err) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + return diags +} + +func resourceFmcDeviceUpdateBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + if d.HasChange("devices") { + _, new := d.GetChange("devices") + newObjects := new.([]interface{}) + + id_mappings := d.Get("id_mappings").([]interface{}) + + // Check if objects are added in the update cycle + if len(id_mappings) < len(newObjects) { + Log.info("New objects found") + idMappingsMap := make(map[string]interface{}) + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + idMappingsMap[idMapping["name"].(string)] = struct{}{} + } + var newObjectsArray []interface{} + for _, newObject := range newObjects { + newObject := newObject.(map[string]interface{}) + if _, found := idMappingsMap[newObject["name"].(string)]; !found { + newObjectsArray = append(newObjectsArray, newObject) + } + } + + // Create the new objects + obj := DeviceBulk{ + Devices: make([]Device, len(newObjectsArray)), + } + + for ind, device := range newObjectsArray { + device := device.(map[string]interface{}) + Log.info(device) + var accpolicy *AccessPolicyItem + dynamicObjects2 := []**AccessPolicyItem{ + &accpolicy, + } + + if inputEntries, ok := device["access_policy"]; ok { + entry := inputEntries.([]interface{})[0].(map[string]interface{}) + + *dynamicObjects2[0] = &AccessPolicyItem{ + ID: entry["id"].(string), + Type: entry["type"].(string), + } + } + + lcap := []string{} + for _, lic := range device["license_caps"].([]interface{}) { + lcap = append(lcap, lic.(string)) + } + + obj.Devices[ind] = Device{ + Name: device["name"].(string), + HostName: device["hostname"].(string), + Type: device_type, + RegKey: device["regkey"].(string), + NatID: device["nat_id"].(string), + PerformanceTier: device["performance_tier"].(string), + LicenseCaps: lcap, + AccessPolicy: accpolicy, + } + } + + res, err := c.CreateFmcDeviceBulk(ctx, &obj) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to create network object", + Detail: err.Error(), + }) + return diags + } + + id_array_map = d.Get("id_mappings").([]interface{}) + for _, o := range res.DeviceResponses { + id_array_map = append(id_array_map, map[string]interface{}{ + "id": o.ID, + "name": o.Name, + }) + } + + d.Set("id_mappings", id_array_map) + } + + // Check if objects are deleted in the update cycle + if len(id_mappings) > len(newObjects) { + // Delete stuff now. + idMappingsMap := make(map[string]interface{}) + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + idMappingsMap[idMapping["name"].(string)] = struct{}{} + } + + var missingKeys []string + // Check for missing keys in the new objects + for key := range idMappingsMap { + found := false + + for _, newObject := range newObjects { + newObject := newObject.(map[string]interface{}) + if newObject["name"].(string) == key { + found = true + break + } + } + + if !found { + missingKeys = append(missingKeys, key) + } + } + + // Delete the missing keys + for _, key := range missingKeys { + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + if idMapping["name"].(string) == key { + err := c.DeleteFmcDevice(ctx, m, idMapping["id"].(string), idMapping["name"].(string), d.Get("cdo_host").(string), d.Get("cdo_region").(string)) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete Device", + Detail: err.Error(), + }) + return diags + } + } + } + } + + // Remove the keys from the id_mappings + var newIdMappings []interface{} + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + found := false + for _, key := range missingKeys { + if idMapping["name"].(string) == key { + found = true + break + } + } + if !found { + newIdMappings = append(newIdMappings, idMapping) + } + } + + Log.info("New id_mappings: ", newIdMappings) + // Update the id_mappings + d.Set("id_mappings", newIdMappings) + } + + // Check if objects are updated in the update cycle + for i := 0; i < len(id_mappings) && i < len(newObjects); i++ { + elem := id_mappings[i] + newObject := newObjects[i].(map[string]interface{}) + + // Rest of the code for processing elem and newObject + id := elem.(map[string]interface{})["id"].(string) + item, err := c.GetFmcDevice(ctx, id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device", + Detail: err.Error(), + }) + return diags + } + obj := map[string]interface{}{ + "name": item.Name, + "id": item.ID, + "hostname": item.HostName, + "type": item.Type, + } + + // obj["access_policy"] = ConvertStructToMap(obj["access_policy"]) + // interfaceSlice := newObject["access_policy"].([]interface{}) + // newSlice := make([]map[string]interface{}, len(interfaceSlice)) + + // for i, item := range interfaceSlice { + // newSlice[i] = item.(map[string]interface{}) + // } + + // newObject["access_policy"] = newSlice + + keysToCheck := []string{"name", "hostname", "type", "performance_tier"} + if !isEqual(obj, newObject, keysToCheck...) { + // Update the object + Log.info("changed object: ", newObject) + lcap := []string{} + for _, lic := range newObject["license_caps"].([]interface{}) { + lcap = append(lcap, lic.(string)) + } + + res, err := c.UpdateFmcDevice(ctx, id, &Device{ + ID: id, + Name: newObject["name"].(string), + HostName: newObject["hostname"].(string), + PerformanceTier: newObject["performance_tier"].(string), + Type: device_type, + LicenseCaps: lcap, + }) + Log.info(res) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to add device", + Detail: err.Error(), + }) + return diags + } + + } + } + } + return resourceFmcDeviceReadBulk(ctx, d, m) +} + +func resourceFmcDeviceDeleteBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + var ids string + id_array := d.Get("id_mappings").([]interface{}) + for _, id := range id_array { + ids = ids + id.(map[string]interface{})["id"].(string) + "," + } + ids = strings.TrimSuffix(ids, ",") + + err := c.DeleteFmcDeviceBulk(ctx, ids) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete device", + Detail: err.Error(), + }) + return diags + } + + // d.SetId("") is automatically called assuming delete returns no errors, but + // it is added here for explicitness. + + d.Set("id_mappings", []interface{}{}) + d.Set("devices", []interface{}{}) + d.SetId("") + + return diags +} diff --git a/fmc/resource_fmc_device_cluster.go b/fmc/resource_fmc_device_cluster.go new file mode 100644 index 00000000..87fd1d06 --- /dev/null +++ b/fmc/resource_fmc_device_cluster.go @@ -0,0 +1,457 @@ +package fmc + +import ( + "context" + // "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var cluster_type string = "DeviceCluster" + +func resourceFmcDeviceCluster() *schema.Resource { + return &schema.Resource{ + Description: "Resource for adding device in a cluster\n" + + "\n" + + "**Note: this will only work on VMware, not on public cloud.**\n" + + "## Example\n" + + "An example is shown below: \n" + + "```hcl\n" + + " resource \"fmc_device_cluster\" \"cluster\" { \n"+ + " name = \"ftd_cluster\" \n"+ + " control_device {\n"+ + " cluster_node_bootstrap {\n"+ + " priority = 1\n"+ + " cclip = \"10.10.11.1\"\n"+ + " }\n"+ + " device_details {\n"+ + " id = data.fmc_devices.device1.id\n"+ + " name = data.fmc_devices.device1.name\n"+ + " }\n"+ + " }\n"+ + " common_bootstrap {\n"+ + " ccl_interface {\n" + + " id = data.fmc_device_physical_interfaces.ccl_physical_interface.id\n" + + " name = data.fmc_device_physical_interfaces.ccl_physical_interface.name\n" + + " }\n" + + " ccl_network = \"10.10.11.0/27\"\n" + + " vni_network = \"10.10.10.0/27\"\n" + + " }\n" + + " data_devices{\n" + + " cluster_node_bootstrap{\n" + + " priority = 2\n" + + " cclip = \"10.10.11.2\"\n" + + " }\n" + + " device_details{\n" + + " id = data.fmc_devices.device2.id\n" + + " name = data.fmc_devices.device2.name\n" + + " }\n" + + " }\n" + + " data_devices{\n" + + " cluster_node_bootstrap{\n" + + " priority = 3\n" + + " cclip = \"10.10.11.3\"\n" + + " }\n" + + " device_details{\n" + + " id = data.fmc_devices.device3.id\n" + + " name = data.fmc_devices.device3.name\n" + + " }\n" + + " }\n" + + " }\n" + + "**Note:** This feature is only supported for VMWare cloud platform.\n"+ + "**Note:** If creating multiple rules during a single `terraform apply`, remember to use `depends_on` to chain the rules so that terraform creates it in the same order that you intended.\n" + + "**Note:** Deleting a cluster will delete all it's data nodes with control node as well.\n" + + "```", + CreateContext: resourceFmcDeviceClusterCreate, + ReadContext: resourceFmcDeviceClusterRead, + UpdateContext: resourceFmcDeviceClusterUpdate, + DeleteContext: resourceFmcDeviceClusterDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: "The name of cluster", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of this resource", + }, + "control_device": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_node_bootstrap": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "priority": { + Type: schema.TypeInt, + Optional: true, + Description: "Set the priority of cluster node", + }, + "cclip": { + Type: schema.TypeString, + Required: true, + Description: "Cluster control IP", + }, + }, + }, + }, + "device_details": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + Description: "Device ID", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Device Name", + }, + "type":{ + Type: schema.TypeString, + Computed: true, + Description: "Device Type", + }, + }, + }, + }, + }, + }, + Description: "Control device information", + }, + "data_devices": { + Type: schema.TypeList, + Optional: true, + MaxItems: 500, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_node_bootstrap": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "priority": { + Type: schema.TypeInt, + Optional: true, + Description: "Set the priority of cluster node", + }, + "cclip": { + Type: schema.TypeString, + Optional: true, + Description: "Cluster control IP", + }, + }, + }, + }, + "device_details": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Description: "Device ID", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Device Name", + }, + "type":{ + Type: schema.TypeString, + Computed: true, + Description: "Device Type", + }, + }, + }, + }, + }, + }, + Description: "Data device information", + }, + "common_bootstrap": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ccl_interface": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Description: "Interface ID", + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Interface Name", + }, + "type":{ + Type: schema.TypeString, + Computed: true, + Description: "Interface Type", + }, + }, + }, + }, + "ccl_network": { + Type: schema.TypeString, + Optional: true, + Description: "Cluster control network", + }, + "vni_network": { + Type: schema.TypeString, + Optional: true, + Description: "VNI network", + }, + }, + }, + Description: "Control device information", + }, + }, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func resourceFmcDeviceClusterCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + // Warning or errors can be collected in a slice type + // var diags diag.Diagnostics + var diags diag.Diagnostics + + var control_device = ControlDevice{} + + if d.Get("control_device") != nil { + if ControlEntries, ok := d.GetOk("control_device"); ok { + entries := ControlEntries.([]interface{})[0] + controlEntry := entries.(map[string]interface{}) + + if controlEntry["cluster_node_bootstrap"] != nil { + node_bootstraps := controlEntry["cluster_node_bootstrap"].([]interface{}) + if len(node_bootstraps) > 0 { + node_bootstrap := node_bootstraps[0].(map[string]interface{}) + var ClusterNodeBootstrap = ClusterNodeBootstrap{ + CclIP: node_bootstrap["cclip"].(string), + Priority: node_bootstrap["priority"].(int), + } + control_device.ClusterNode = &ClusterNodeBootstrap + } + + } + + if controlEntry["device_details"] != nil { + + details := controlEntry["device_details"].([]interface{}) + detail := details[0].(map[string]interface{}) + + var DeviceDetails = DeviceDetails{ + ID: detail["id"].(string), + Name: detail["name"].(string), + Type: "Device", + } + control_device.DeviceDetail = &DeviceDetails + } + + } + + } + + // Funky stuff starts here + + dataDevices := d.Get("data_devices").([]interface{}) + Log.info(dataDevices) + + obj := Cluster{ + DataDevices: make([]ControlDevice, len(dataDevices)), + } + for ind, node := range dataDevices { + node := node.(map[string]interface{}) + var devDet *DeviceDetails + dynamicObjects2 := []**DeviceDetails{ + &devDet, + } + + if inputEntries, ok := node["device_details"]; ok { + entry := inputEntries.([]interface{})[0].(map[string]interface{}) + *dynamicObjects2[0] = &DeviceDetails{ + ID: entry["id"].(string), + Type: "Device", + Name: entry["name"].(string), + } + } + + var clusno *ClusterNodeBootstrap + dynamicObjects3 := []**ClusterNodeBootstrap{ + &clusno, + } + + if inputEntries, ok := node["cluster_node_bootstrap"]; ok { + entry := inputEntries.([]interface{})[0].(map[string]interface{}) + *dynamicObjects3[0] = &ClusterNodeBootstrap{ + CclIP: entry["cclip"].(string), + Priority: entry["priority"].(int), + } + } + + obj.DataDevices[ind] = ControlDevice{ + ClusterNode: clusno, + DeviceDetail: devDet, + } + } + // Funky stuff ends here + var common_bootstrap = CommonBootstrap{} + if d.Get("common_bootstrap") != nil { + if interfaceEntries, ok := d.GetOk("common_bootstrap"); ok { + entries := interfaceEntries.([]interface{})[0] + interfaceEntry := entries.(map[string]interface{}) + + if interfaceEntry["ccl_interface"] != nil { + interafaces := interfaceEntry["ccl_interface"].([]interface{}) + if len(interafaces) > 0 { + interfac := interafaces[0].(map[string]interface{}) + var CclInterface = DeviceDetails{ + ID: interfac["id"].(string), + Name: interfac["name"].(string), + Type: "PhysicalInterface", + } + common_bootstrap.CclInterface = &CclInterface + } + + } + common_bootstrap.CclNetwork = interfaceEntry["ccl_network"].(string) + common_bootstrap.VniNetwork = interfaceEntry["vni_network"].(string) + } + + } + _, err := c.CreateFmcDeviceCluster(ctx, &Cluster{ + Name: d.Get("name").(string), + Type: cluster_type, + ControlDevice: control_device, + CommonBootstrap: common_bootstrap, + DataDevices : obj.DataDevices, + }) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to add device", + Detail: err.Error(), + }) + return diags + } + //ADD CODE TO GET ID + cluster, err := c.GetFmcDeviceClusterByName(ctx, d.Get("name").(string)) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to get cluster", + Detail: err.Error(), + }) + return diags + } + d.SetId(cluster.ID) + + //Code ENDED + return resourceFmcDeviceClusterRead(ctx, d, m) +} + +func resourceFmcDeviceClusterRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + id := d.Id() + item, err := c.GetFmcDeviceCluster(ctx, id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device", + Detail: err.Error(), + }) + return diags + } + if err := d.Set("name", item.Name); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device", + Detail: err.Error(), + }) + return diags + } + + if err := d.Set("type", item.Type); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read device", + Detail: err.Error(), + }) + return diags + } + + return diags +} + +func resourceFmcDeviceClusterUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + // Warning or errors can be collected in a slice type + // var diags diag.Diagnostics + var diags diag.Diagnostics + if d.HasChanges("name") { + + res, err := c.UpdateFmcDeviceCluster(ctx, d.Id(), &Cluster{ + ID: d.Id(), + Name: d.Get("name").(string), + Action: "UPDATE_CLUSTER_NAME", + + }) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to create cluster", + Detail: err.Error(), + }) + return diags + } + d.SetId(res.ID) + } + return resourceFmcDeviceClusterRead(ctx, d, m) +} + +func resourceFmcDeviceClusterDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + id := d.Id() + + err := c.DeleteFmcDeviceCluster(ctx, m, id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete cluster", + Detail: err.Error(), + }) + return diags + } + + // d.SetId("") is automatically called assuming delete returns no errors, but + // it is added here for explicitness. + d.SetId("") + + return diags +} diff --git a/fmc/resource_fmc_device_subinterfaces.go b/fmc/resource_fmc_device_subinterfaces.go new file mode 100644 index 00000000..3e40ae21 --- /dev/null +++ b/fmc/resource_fmc_device_subinterfaces.go @@ -0,0 +1,366 @@ +package fmc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceFmcSubInterface() *schema.Resource { + return &schema.Resource{ + Description: "Resource for SubInterfaces in FMC\n" + + "\n" + + "## Example\n" + + "An example is shown below: \n" + + "```hcl\n" + + "resource \"fmc_device_subinterfaces\" \"sub\" {\n" + + " name = \"GigabitEthernet0/1\"\n" + + " device_id = \"\"\n" + + " subinterface_id = 1\n" + + " vlan_id = 30\n" + + " security_zone_id = \"\"\n" + + " if_name = \"Inside\"\n" + + " mtu = 1700\n" + + " mode = \"NONE\"\n" + + " enabled = true\n" + + " ipv4_static_address = \"10.20.220.45\"\n" + + " ipv4_static_netmask = 24\n" + + " ipv4_dhcp_enabled = false\n" + + " ipv4_dhcp_route_metric = 1\n" + + " ipv6_address = \"2001:10:240:ac::a\"\n" + + " ipv6_prefix = 124\n" + + " ipv6_enforce_eui = false\n" + + "}\n" + + "```", + CreateContext: resourceFmcSubInterfaceCreate, + ReadContext: resourceFmcSubInterfaceRead, + UpdateContext: resourceFmcSubInterfaceUpdate, + DeleteContext: resourceFmcSubInterfaceDelete, + Schema: map[string]*schema.Schema{ + "device_id": { + Type: schema.TypeString, + Required: true, + Description: "The ID of the device this resource needs", + }, + "subinterface_id": { + Type: schema.TypeInt, + Required: true, + Description: "The ID this resource needs", + }, + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "The Vlan ID needed for this resource", + }, + "ifname": { + Type: schema.TypeString, + Optional: true, + Description: "Name of chosen interface", + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "Enable this resource", + }, + "management_only": { + Type: schema.TypeBool, + Optional: true, + Description: "Managenment only or not", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of this resource", + }, + "mode": { + Type: schema.TypeString, + Optional: true, + Default: "NONE", + Description: "The mode of this resource", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "Name of the physical interface", + }, + "mtu": { + Type: schema.TypeInt, + Optional: true, + Default: 1500, + Description: "The mtu value", + }, + "priority": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + Description: "The type of this resource", + }, + "security_zone_id": { + Type: schema.TypeString, + Optional: true, + Description: "Security Zone ID", + }, + "ipv4_static_address": { + Type: schema.TypeString, + Optional: true, + Description: "IPv4 Static address", + }, + "ipv4_static_netmask": { + Type: schema.TypeInt, + Optional: true, + Description: "IPv4 Static address netmask", + }, + "ipv4_dhcp_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "IPv4 DHCP enabled", + }, + "ipv4_dhcp_route_metric": { + Type: schema.TypeInt, + Optional: true, + Description: "IPv4 DHCP Route Metric", + }, + "enable_ipv6": { + Type: schema.TypeBool, + Optional: true, + Description: "IPv6 address enabled", + }, + "ipv6_address": { + Type: schema.TypeString, + Optional: true, + Description: "IPv6 address", + }, + "ipv6_prefix": { + Type: schema.TypeInt, + Optional: true, + Description: "IPv6 netmask", + }, + "ipv6_enforce_eui": { + Type: schema.TypeBool, + Optional: true, + Description: "IPv6 EnforceEUI64", + }, + }, + } +} + +func resourceFmcSubInterfaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + id := d.Id() + item, err := c.GetFmcSubInterface(ctx, d.Get("device_id").(string), id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read physical interface", + Detail: err.Error(), + }) + return diags + } + if err := d.Set("ifname", item.Ifname); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read physical interface", + Detail: err.Error(), + }) + return diags + } + if err := d.Set("name", item.Name); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read physical interface", + Detail: err.Error(), + }) + return diags + } + + if err := d.Set("type", item.Type); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read host object", + Detail: err.Error(), + }) + return diags + } + return diags +} +func resourceFmcSubInterfaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + var diags diag.Diagnostics + + + ipv4StaticAddress := d.Get("ipv4_static_address").(string) + ipv4StaticNetmask := d.Get("ipv4_static_netmask").(int) + ipv4DhcpEnabled := d.Get("ipv4_dhcp_enabled").(bool) + ipv4DhcpRouteMetric := d.Get("ipv4_dhcp_route_metric").(int) + + securityZoneId := d.Get("security_zone_id").(string) + + ipv6Address := d.Get("ipv6_address").(string) + ipv6Prefix := d.Get("ipv6_prefix").(int) + ipv6EnforceEUI := d.Get("ipv6_enforce_eui").(bool) + enable_ipv6 := d.Get("enable_ipv6").(bool) + var IPv6Add []IPv6Address + + if len(ipv6Address) > 0 { + IPv6Add = append(IPv6Add, IPv6Address{ + Address: ipv6Address, + Prefix: ipv6Prefix, + EnforceEUI64: ipv6EnforceEUI, + }) + } + + var IPv6 = IPv6{EnableIPv6:enable_ipv6 ,Addresses: IPv6Add} + + var SubInterfaceSecurityZone = PhysicalInterfaceSecurityZone{ + ID: securityZoneId, + Type: "SecurityZone", + } + var IPv4Static = IPv4Static{ + Address: ipv4StaticAddress, + Netmask: ipv4StaticNetmask, + } + var IPv4DHCP = IPv4DHCP{ + Enable: ipv4DhcpEnabled, + RouteMetric: ipv4DhcpRouteMetric, + } + + var IPv4 = IPv4{} + + if ipv4DhcpEnabled { + IPv4.DHCP = &IPv4DHCP + } else if len(ipv4StaticAddress) > 0 { + IPv4.Static = &IPv4Static + } + res, err := c.CreateFmcSubInterface(ctx, d.Get("device_id").(string), &SubInterface{ + Ifname: d.Get("ifname").(string), + Mode: d.Get("mode").(string), + Name: d.Get("name").(string), + IPv4: IPv4, + VlanID: d.Get("vlan_id").(int), + SubInterfaceID: d.Get("subinterface_id").(int), + Enabled: d.Get("enabled").(bool), + SecurityZone: SubInterfaceSecurityZone, + MgmntOnly: d.Get("management_only").(bool), + Priority: d.Get("priority").(int), + MTU: d.Get("mtu").(int), + IPv6: IPv6, + }) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to update physical interface", + Detail: err.Error(), + }) + return diags + } + + d.SetId(res.ID) + return resourceFmcSubInterfaceRead(ctx, d, m) +} +func resourceFmcSubInterfaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + var diags diag.Diagnostics + id := d.Id() + if d.HasChanges("ipv6_enforce_eui","ipv6_prefix","ipv6_address","vlan_id","management_only","ifname","name", "mode", "ipv4_static_address", "security_zone_id", "ipv4_dhcp_enabled","ipv4_dhcp_route_metric", "priority", "enabled") { + + ipv4StaticAddress := d.Get("ipv4_static_address").(string) + ipv4StaticNetmask := d.Get("ipv4_static_netmask").(int) + ipv4DhcpEnabled := d.Get("ipv4_dhcp_enabled").(bool) + ipv4DhcpRouteMetric := d.Get("ipv4_dhcp_route_metric").(int) + securityZoneId := d.Get("security_zone_id").(string) + enable_ipv6 := d.Get("enable_ipv6").(bool) + + var SubInterfaceSecurityZone = PhysicalInterfaceSecurityZone{ + ID: securityZoneId, + Type: "SecurityZone", + } + var IPv4Static = IPv4Static{ + Address: ipv4StaticAddress, + Netmask: ipv4StaticNetmask, + } + var IPv4DHCP = IPv4DHCP{ + Enable: ipv4DhcpEnabled, + RouteMetric: ipv4DhcpRouteMetric, + } + + var IPv4 = IPv4{} + + if ipv4DhcpEnabled { + IPv4.DHCP = &IPv4DHCP + } else if len(ipv4StaticAddress) > 0 { + IPv4.Static = &IPv4Static + } + ipv6Address := d.Get("ipv6_address").(string) + ipv6Prefix := d.Get("ipv6_prefix").(int) + ipv6EnforceEUI := d.Get("ipv6_enforce_eui").(bool) + + var IPv6Add []IPv6Address + + if len(ipv6Address) > 0 { + IPv6Add = append(IPv6Add, IPv6Address{ + Address: ipv6Address, + Prefix: ipv6Prefix, + EnforceEUI64: ipv6EnforceEUI, + }) + } + + var IPv6 = IPv6{EnableIPv6:enable_ipv6 ,Addresses: IPv6Add} + + _, err := c.UpdateFmcSubInterface(ctx, d.Get("device_id").(string), id, &SubInterface{ + ID: id, + Ifname: d.Get("ifname").(string), + Mode: d.Get("mode").(string), + Name: d.Get("name").(string), + IPv4: IPv4, + VlanID: d.Get("vlan_id").(int), + Enabled: d.Get("enabled").(bool), + SecurityZone: SubInterfaceSecurityZone, + SubInterfaceID: d.Get("subinterface_id").(int), + MgmntOnly: d.Get("management_only").(bool), + Priority: d.Get("priority").(int), + MTU: d.Get("mtu").(int), + IPv6: IPv6, + }) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to update physical interface", + Detail: err.Error(), + }) + return diags + } + } + return resourceFmcSubInterfaceRead(ctx, d, m) +} + +func resourceFmcSubInterfaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + id := d.Id() + + err := c.DeleteFmcSubInterface(ctx, d.Get("device_id").(string) ,id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete subinterface", + Detail: err.Error(), + }) + return diags + } + + // d.SetId("") is automatically called assuming delete returns no errors, but + // it is added here for explicitness. + d.SetId("") + + return diags +} diff --git a/fmc/resource_fmc_device_subinterfaces_test.go b/fmc/resource_fmc_device_subinterfaces_test.go new file mode 100644 index 00000000..742405ca --- /dev/null +++ b/fmc/resource_fmc_device_subinterfaces_test.go @@ -0,0 +1,97 @@ +package fmc + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccFmcSubInterfaceCreateBasic(t *testing.T) { + device := "ftd.adyah.cisco" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckFmcSubInterfaceCreateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckFmcSubInterfaceCreateConfigBasic(device), + Check: resource.ComposeTestCheckFunc( + testAccCheckFmcSubInterfaceCreateExists("fmc_device_subinterfaces.sub"), + ), + }, + }, + }) +} + +func testAccCheckFmcSubInterfaceCreateDestroy(s *terraform.State) error { + c := testAccProvider.Meta().(*Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "fmc_device_subinterfaces" { + continue + } + + id := rs.Primary.ID + ctx := context.Background() + err := c.DeleteFmcSubInterface(ctx, rs.Primary.Attributes["device_id"], id) + + // Object is already deleted + if err != nil && !strings.Contains(fmt.Sprint(err), "404") { + return err + } + } + + return nil +} + +func testAccCheckFmcSubInterfaceCreateConfigBasic(device string) string { + return fmt.Sprintf(` + data "fmc_devices" "device" { + name = "%s" + } + resource "fmc_security_zone" "outside" { + name = "outside" + interface_mode = "ROUTED" + } + + resource "fmc_device_subinterfaces" "sub" { + device_id = data.fmc_devices.device.id + ifname = "Testing1" + subinterface_id = 12345 + vlan_id = 80 + name = "TenGigabitEthernet0/1" + mode = "NONE" + mtu = 1600 + enabled = true + priority = 69 + security_zone_id = fmc_security_zone.outside.id + ipv4_dhcp_enabled = false + ipv4_dhcp_route_metric = 1 + enable_ipv6 = true + ipv6_address = "2001:10:240:ac::a" + ipv6_prefix = "124" + ipv6_enforce_eui = false + } + `, device) +} + +func testAccCheckFmcSubInterfaceCreateExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set") + } + + return nil + } +} diff --git a/fmc/resource_fmc_network_objects_bulk.go b/fmc/resource_fmc_network_objects_bulk.go new file mode 100644 index 00000000..c41c9cff --- /dev/null +++ b/fmc/resource_fmc_network_objects_bulk.go @@ -0,0 +1,387 @@ +package fmc + +import ( + "context" + "strconv" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var id_array_map []interface{} + +func resourceFmcNetworkObjectsBulk() *schema.Resource { + return &schema.Resource{ + Description: "Resource for Network Objects in FMC\n" + + "\n" + + "## Example\n" + + "An example is shown below: \n" + + "```hcl\n" + + "locals {\n" + + "object_names = [for i in range(3) : \"VLAN825-Private-DRsite-${i}\"]\n" + + "}\n" + + "resource \"fmc_network_objects_bulk\" \"test\" {\n" + + "dynamic \"objects\" {\n" + + "for_each = local.object_names\n" + + + "content {\n" + + " name = objects.value\n" + + " value = data.fmc_network_objects.PrivateVLAN.value\n" + + " description = \"testing terraform\"\n" + + "}\n" + + "}\n" + + "}\n" + + "```", + CreateContext: resourceFmcNetworkObjectsCreateBulk, + ReadContext: resourceFmcNetworkObjectsReadBulk, + UpdateContext: resourceFmcNetworkObjectsUpdateBulk, + DeleteContext: resourceFmcNetworkObjectsDeleteBulk, + Schema: map[string]*schema.Schema{ + "objects": { + Type: schema.TypeList, + Required: true, + MaxItems: 100, + MinItems: 1, + Description: "The list of network objects to create", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of this resource", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of this resource", + }, + "value": { + Type: schema.TypeString, + Required: true, + Description: "The value of this resource", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type this resource", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of this resource", + }, + }, + }, + }, + + "id_mappings": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The id of this resource", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of this resource", + }, + }, + }, + Description: "id array", + }, + }, + } +} + +func resourceFmcNetworkObjectsCreateBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + // Warning or errors can be collected in a slice type + // var diags diag.Diagnostics + var diags diag.Diagnostics + objects := d.Get("objects").([]interface{}) + + obj := NetworkObjects{ + Objects: make([]NetworkObject, len(objects)), + } + for i, o := range objects { + obj.Objects[i] = NetworkObject{ + Name: o.(map[string]interface{})["name"].(string), + Description: o.(map[string]interface{})["description"].(string), + Value: o.(map[string]interface{})["value"].(string), + Type: network_type, + } + } + + res, err := c.CreateFmcNetworkObjectBulk(ctx, &obj) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to create network objects", + Detail: err.Error(), + }) + return diags + } + + for _, o := range res.Items { + id_array_map = append(id_array_map, map[string]interface{}{ + "id": o.ID, + "name": o.Name, + }) + } + d.Set("id_mappings", id_array_map) + + return resourceFmcNetworkObjectsReadBulk(ctx, d, m) +} + +func resourceFmcNetworkObjectsReadBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + ids := d.Get("id_mappings").([]interface{}) + + var all_obj []interface{} + for _, elem := range ids { + item, err := c.GetFmcNetworkObject(ctx, elem.(map[string]interface{})["id"].(string)) + if err != nil { + if strings.Contains(err.Error(), "404") { + Log.info("Network object not found") + return diags + } else { + return returnWithDiag(diags, err) + } + } + + obj := []interface{}{ + map[string]interface{}{ + "name": item.Name, + "type": item.Type, + "id": item.ID, + "description": item.Description, + "value": item.Value, + }, + } + all_obj = append(all_obj, obj[0]) + } + if err := d.Set("objects", all_obj); err != nil { + return returnWithDiag(diags, err) + } + + d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) + + return diags +} + +func resourceFmcNetworkObjectsUpdateBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + var diags diag.Diagnostics + + if d.HasChanges("objects") { + // newObjects := d.Get("objects").([]interface{}) + _, new := d.GetChange("objects") + newObjects := new.([]interface{}) + + id_mappings := d.Get("id_mappings").([]interface{}) + + // Check if objects are added in the update cycle + if len(id_mappings) < len(newObjects) { + Log.info("New objects found") + idMappingsMap := make(map[string]interface{}) + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + idMappingsMap[idMapping["name"].(string)] = struct{}{} + } + var newObjectsArray []interface{} + for _, newObject := range newObjects { + newObject := newObject.(map[string]interface{}) + if _, found := idMappingsMap[newObject["name"].(string)]; !found { + newObjectsArray = append(newObjectsArray, newObject) + } + } + + // Create the new objects + obj := NetworkObjects{ + Objects: make([]NetworkObject, len(newObjectsArray)), + } + for i, o := range newObjectsArray { + obj.Objects[i] = NetworkObject{ + Name: o.(map[string]interface{})["name"].(string), + Description: o.(map[string]interface{})["description"].(string), + Value: o.(map[string]interface{})["value"].(string), + Type: network_type, + } + } + res, err := c.CreateFmcNetworkObjectBulk(ctx, &obj) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to create network object", + Detail: err.Error(), + }) + return diags + } + + id_array_map = d.Get("id_mappings").([]interface{}) + for _, o := range res.Items { + id_array_map = append(id_array_map, map[string]interface{}{ + "id": o.ID, + "name": o.Name, + }) + } + + d.Set("id_mappings", id_array_map) + } + + // Check if objects are deleted in the update cycle + if len(id_mappings) > len(newObjects) { + // Delete stuff now. + idMappingsMap := make(map[string]interface{}) + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + idMappingsMap[idMapping["name"].(string)] = struct{}{} + } + + var missingKeys []string + // Check for missing keys in the new objects + for key := range idMappingsMap { + found := false + + for _, newObject := range newObjects { + newObject := newObject.(map[string]interface{}) + if newObject["name"].(string) == key { + found = true + break + } + } + + if !found { + missingKeys = append(missingKeys, key) + } + } + + // Delete the missing keys + for _, key := range missingKeys { + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + if idMapping["name"].(string) == key { + err := c.DeleteFmcNetworkObject(ctx, idMapping["id"].(string)) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete network object", + Detail: err.Error(), + }) + return diags + } + } + } + } + + // Remove the keys from the id_mappings + var newIdMappings []interface{} + for _, idMapping := range id_mappings { + idMapping := idMapping.(map[string]interface{}) + found := false + for _, key := range missingKeys { + if idMapping["name"].(string) == key { + found = true + break + } + } + if !found { + newIdMappings = append(newIdMappings, idMapping) + } + } + + Log.info("New id_mappings: ", newIdMappings) + // Update the id_mappings + d.Set("id_mappings", newIdMappings) + } + + // Check if objects are updated in the update cycle + for i := 0; i < len(id_mappings) && i < len(newObjects); i++ { + elem := id_mappings[i] + newObject := newObjects[i].(map[string]interface{}) + + // Rest of the code for processing elem and newObject + id := elem.(map[string]interface{})["id"].(string) + item, err := c.GetFmcNetworkObject(ctx, id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to read network object", + Detail: err.Error(), + }) + return diags + } + obj := map[string]interface{}{ + "name": item.Name, + "id": item.ID, + "description": item.Description, + "value": item.Value, + "type": item.Type, + } + + // Rest of the code using obj + keysToCompare := []string{"name", "description", "value"} + if !isEqual(obj, newObject, keysToCompare...) { + Log.info("changed object: ", newObject) + _, err = c.UpdateFmcNetworkObject(ctx, id, &NetworkObjectUpdateInput{ + Name: newObject["name"].(string), + Description: newObject["description"].(string), + Value: newObject["value"].(string), + Type: network_type, + ID: id, + }) + + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to update network object", + Detail: err.Error(), + }) + return diags + } + + // Update the id_mappings + id_mappings[i] = map[string]interface{}{ + "name": newObject["name"].(string), + "id": id, + } + + // Update the data + d.Set("id_mappings", id_mappings) + } + } + } + return resourceFmcNetworkObjectsReadBulk(ctx, d, m) +} + +func resourceFmcNetworkObjectsDeleteBulk(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + c := m.(*Client) + + // Warning or errors can be collected in a slice type + var diags diag.Diagnostics + + ids := d.Get("id_mappings").([]interface{}) + for _, elem := range ids { + id := elem.(map[string]interface{})["id"].(string) + err := c.DeleteFmcNetworkObject(ctx, id) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to delete network object", + Detail: err.Error(), + }) + return diags + } + } + return diags +} diff --git a/fmc/resource_fmc_vtep_test.go b/fmc/resource_fmc_vtep_test.go new file mode 100644 index 00000000..f64363da --- /dev/null +++ b/fmc/resource_fmc_vtep_test.go @@ -0,0 +1,110 @@ +package fmc + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccFmcVtepCreateBasic(t *testing.T) { + device := "ftd.adyah.cisco" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckFmcVtepCreateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckFmcVtepCreateConfigBasic(device), + Check: resource.ComposeTestCheckFunc( + testAccCheckFmcVtepCreateExists("fmc_device_vtep.my_fmc_device_vtep"), + ), + }, + }, + }) +} + +func testAccCheckFmcVtepCreateDestroy(s *terraform.State) error { + c := testAccProvider.Meta().(*Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "fmc_device_vtep" { + continue + } + + id := rs.Primary.ID + ctx := context.Background() + _, err := c.DeleteFmcVTEPDetails(ctx, rs.Primary.Attributes["device_id"], id) + + // Object is already deleted + if err != nil && !strings.Contains(fmt.Sprint(err), "404") { + return err + } + } + + return nil +} + +func testAccCheckFmcVtepCreateConfigBasic(device string) string { + return fmt.Sprintf(` + data "fmc_devices" "device" { + name = "%s" + } + data "fmc_device_physical_interfaces" "zero_physical_interface" { + device_id = data.fmc_devices.device.id + name = "TenGigabitEthernet0/0" + } + resource "fmc_host_objects" "test1" { + name = "test1" + value = "172.16.1.1" + } + resource "fmc_host_objects" "test2" { + name = "test2" + value = "172.16.2.1" + } + + resource "fmc_network_group_objects" "TestPrivateGroup" { + name = "TestPrivateGroup" + description = "Testing groups" + objects { + id = fmc_host_objects.test1.id + type = fmc_host_objects.test1.type + } + objects { + id = fmc_host_objects.test2.id + type = fmc_host_objects.test2.type + } + } + resource "fmc_device_vtep" "my_fmc_device_vtep" { + device_id = data.fmc_devices.device.id + nve_enabled = true + + nve_vtep_id = 1 + nve_encapsulation_type = "VXLAN" + nve_destination_port = 4789 + source_interface_id = data.fmc_device_physical_interfaces.zero_physical_interface.id + + nve_neighbor_discovery_type= "NONE" + } + `, device) +} + +func testAccCheckFmcVtepCreateExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID set") + } + + return nil + } +}