diff --git a/CHANGELOG.md b/CHANGELOG.md index 756b7683..5d61644c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,10 @@ - BREAKING CHANGE: Rename `summary_address` attribute to `summary_addresses` of `iosxe_ospf` resource and data source - Add `ipv4_unicast_networks_mask` and `ipv4_unicast_networks` attribute to `iosxe_bgp_address_family_ipv4` and `iosxe_bgp_address_family_ipv4_vrf` resources and data sources - Add `ipv6_unicast_networks` attribute to `iosxe_bgp_address_family_ipv6` and `iosxe_bgp_address_family_ipv6_vrf` resources and data sources +- Add `iosxe_service_template` resource and data source +- Add `iosxe_vlan_access_map` resource and data source +- Add `service_policy_subscriber` attribute to `iosxe_template` resources and data sources + ## 0.3.3 diff --git a/docs/data-sources/aaa.md b/docs/data-sources/aaa.md index 2fa05011..59ab9906 100644 --- a/docs/data-sources/aaa.md +++ b/docs/data-sources/aaa.md @@ -39,6 +39,7 @@ data "iosxe_aaa" "example" { Read-Only: +- `ip_radius_source_interface_loopback` (Number) Loopback interface - `name` (String) Radius Server-group name with max string length 32 - `server_names` (Attributes List) Name of radius server (see [below for nested schema](#nestedatt--group_server_radius--server_names)) diff --git a/docs/data-sources/aaa_authorization.md b/docs/data-sources/aaa_authorization.md index 7d97b974..dee5ca84 100644 --- a/docs/data-sources/aaa_authorization.md +++ b/docs/data-sources/aaa_authorization.md @@ -35,8 +35,10 @@ data "iosxe_aaa_authorization" "example" { Read-Only: +- `a1_group` (String) Use Server-group - `a1_if_authenticated` (Boolean) Succeed if user has authenticated. - `a1_local` (Boolean) Use local database +- `a2_local` (Boolean) - `name` (String) diff --git a/docs/data-sources/interface_tunnel.md b/docs/data-sources/interface_tunnel.md index eeb79e64..59ba9d93 100644 --- a/docs/data-sources/interface_tunnel.md +++ b/docs/data-sources/interface_tunnel.md @@ -31,6 +31,7 @@ data "iosxe_interface_tunnel" "example" { ### Read-Only +- `arp_timeout` (Number) Set ARP cache timeout - `bfd_echo` (Boolean) Use echo adjunct as bfd detection mechanism - `bfd_enable` (Boolean) Enable BFD under the interface - `bfd_interval` (Number) diff --git a/docs/data-sources/service_template.md b/docs/data-sources/service_template.md new file mode 100644 index 00000000..f8c8d41a --- /dev/null +++ b/docs/data-sources/service_template.md @@ -0,0 +1,79 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_service_template Data Source - terraform-provider-iosxe" +subcategory: "System" +description: |- + This data source can read the Service Template configuration. +--- + +# iosxe_service_template (Data Source) + +This data source can read the Service Template configuration. + +## Example Usage + +```terraform +data "iosxe_service_template" "example" { + word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE" +} +``` + + +## Schema + +### Required + +- `word` (String) Specify a template name (maximum 48 characters) + +### Optional + +- `device` (String) A device name from the provider configuration. + +### Read-Only + +- `absolute_timer` (Number) Enter a value between 1 and 1073741823 +- `access_group` (Attributes List) Access list to be applied (see [below for nested schema](#nestedatt--access_group)) +- `description` (String) Enter a description +- `dns_acl_preauth` (String) pre-authentication +- `id` (String) The path of the retrieved object. +- `inactivity_timer_probe` (Boolean) ARP probe +- `inactivity_timer_value` (Number) Enter a value between 1 and 65535 +- `interface_template` (Attributes List) Interface template to be applied (see [below for nested schema](#nestedatt--interface_template)) +- `linksec_policy` (String) Set the link security policy +- `mdns_service_policy` (String) mdns policy to be applied +- `redirect_append_client_mac` (String) Append client Mac Address in redirect URL +- `redirect_append_switch_mac` (String) Append switch Mac Address in redirect URL +- `redirect_url_match_acl_name` (String) Specify the access list name +- `redirect_url_match_action` (String) +- `redirect_url_url_name` (String) Specify a valid URL +- `service_policy_qos_input` (String) Configure input Qos policy +- `service_policy_qos_output` (String) Configure output Qos policy +- `sgt` (Number) SGT tag +- `tag_config` (Attributes List) tag name (see [below for nested schema](#nestedatt--tag_config)) +- `tunnel_capwap_name` (String) tunnel profile name +- `vlan` (Number) Vlan to be applied +- `vnid` (String) Vnid to be applied +- `voice_vlan` (Boolean) Critical voice vlan + + +### Nested Schema for `access_group` + +Read-Only: + +- `name` (String) Specify the access list name + + + +### Nested Schema for `interface_template` + +Read-Only: + +- `name` (String) Enter name of interface template + + + +### Nested Schema for `tag_config` + +Read-Only: + +- `name` (String) Specify the Tag name diff --git a/docs/data-sources/template.md b/docs/data-sources/template.md index 5ab87f7a..3aa2baf4 100644 --- a/docs/data-sources/template.md +++ b/docs/data-sources/template.md @@ -52,6 +52,7 @@ data "iosxe_template" "example" { - `dot1x_max_reauth_req` (Number) Max No. of Reauthentication Attempts - `dot1x_max_req` (Number) Max No. of Retries - `dot1x_pae` (String) Set 802.1x interface pae type +- `dot1x_timeout_tx_period` (Number) Timeout for supplicant retries - `id` (String) The path of the retrieved object. - `ip_access_group` (Attributes List) Access control list for IP packets (see [below for nested schema](#nestedatt--ip_access_group)) - `ip_dhcp_snooping_limit_rate` (Number) DHCP snooping rate limit @@ -61,6 +62,7 @@ data "iosxe_template" "example" { - `mab_eap` (Boolean) Use EAP authentication for MAC Auth Bypass - `service_policy_input` (String) policy-map name - `service_policy_output` (String) policy-map name +- `service_policy_subscriber` (String) Apply a subscriber control policy to the interface - `source_template` (String) Get config from a template - `spanning_tree_bpduguard_enable` (Boolean) Enable BPDU guard for this interface - `spanning_tree_portfast` (Boolean) Portfast options for the interface diff --git a/docs/data-sources/vlan_access_map.md b/docs/data-sources/vlan_access_map.md new file mode 100644 index 00000000..99f88b54 --- /dev/null +++ b/docs/data-sources/vlan_access_map.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_vlan_access_map Data Source - terraform-provider-iosxe" +subcategory: "Switching" +description: |- + This data source can read the VLAN Access Map configuration. +--- + +# iosxe_vlan_access_map (Data Source) + +This data source can read the VLAN Access Map configuration. + +## Example Usage + +```terraform +data "iosxe_vlan_access_map" "example" { + name = "accessmap1" + value = 1000 +} +``` + + +## Schema + +### Required + +- `name` (String) Vlan access map tag +- `value` (Number) Sequence to insert to/delete from existing vlan access-map entry + +### Optional + +- `device` (String) A device name from the provider configuration. + +### Read-Only + +- `action` (String) Take the action +- `id` (String) The path of the retrieved object. +- `match_ip_address` (List of String) +- `match_ipv6_address` (List of String) Match IPv6 address to access control. diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 33853c1b..022d03f0 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -69,6 +69,10 @@ description: |- - BREAKING CHANGE: Rename `summary_address` attribute to `summary_addresses` of `iosxe_ospf` resource and data source - Add `ipv4_unicast_networks_mask` and `ipv4_unicast_networks` attribute to `iosxe_bgp_address_family_ipv4` and `iosxe_bgp_address_family_ipv4_vrf` resources and data sources - Add `ipv6_unicast_networks` attribute to `iosxe_bgp_address_family_ipv6` and `iosxe_bgp_address_family_ipv6_vrf` resources and data sources +- Add `iosxe_service_template` resource and data source +- Add `iosxe_vlan_access_map` resource and data source +- Add `service_policy_subscriber` attribute to `iosxe_template` resources and data sources + ## 0.3.3 diff --git a/docs/resources/aaa.md b/docs/resources/aaa.md index f55b23bc..e360e80b 100644 --- a/docs/resources/aaa.md +++ b/docs/resources/aaa.md @@ -32,6 +32,7 @@ resource "iosxe_aaa" "example" { name = "TESTRADIUS" } ] + ip_radius_source_interface_loopback = 0 } ] group_tacacsplus = [ @@ -74,6 +75,8 @@ Required: Optional: +- `ip_radius_source_interface_loopback` (Number) Loopback interface + - Range: `0`-`2147483647` - `server_names` (Attributes List) Name of radius server (see [below for nested schema](#nestedatt--group_server_radius--server_names)) diff --git a/docs/resources/aaa_authorization.md b/docs/resources/aaa_authorization.md index 9af40c6e..1243707b 100644 --- a/docs/resources/aaa_authorization.md +++ b/docs/resources/aaa_authorization.md @@ -18,6 +18,7 @@ resource "iosxe_aaa_authorization" "example" { { name = "TEST" a1_local = false + a1_group = "false" a1_if_authenticated = true } ] @@ -48,8 +49,10 @@ Required: Optional: +- `a1_group` (String) Use Server-group - `a1_if_authenticated` (Boolean) Succeed if user has authenticated. - `a1_local` (Boolean) Use local database +- `a2_local` (Boolean) diff --git a/docs/resources/interface_tunnel.md b/docs/resources/interface_tunnel.md index e9a93106..bd00758a 100644 --- a/docs/resources/interface_tunnel.md +++ b/docs/resources/interface_tunnel.md @@ -39,6 +39,7 @@ resource "iosxe_interface_tunnel" "example" { ] tunnel_destination_ipv4 = "2.2.2.2" crypto_ipsec_df_bit = "clear" + arp_timeout = 300 ipv4_address = "10.1.1.1" ipv4_address_mask = "255.255.255.0" ip_dhcp_relay_source_interface = "Loopback100" @@ -65,6 +66,8 @@ resource "iosxe_interface_tunnel" "example" { ### Optional +- `arp_timeout` (Number) Set ARP cache timeout + - Range: `0`-`2147483` - `bfd_echo` (Boolean) Use echo adjunct as bfd detection mechanism - `bfd_enable` (Boolean) Enable BFD under the interface - `bfd_interval` (Number) - Range: `50`-`9999` diff --git a/docs/resources/service_template.md b/docs/resources/service_template.md new file mode 100644 index 00000000..659e6b91 --- /dev/null +++ b/docs/resources/service_template.md @@ -0,0 +1,125 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_service_template Resource - terraform-provider-iosxe" +subcategory: "System" +description: |- + This resource can manage the Service Template configuration. +--- + +# iosxe_service_template (Resource) + +This resource can manage the Service Template configuration. + +## Example Usage + +```terraform +resource "iosxe_service_template" "example" { + word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE" + access_group = [ + { + name = "ag1" + } + ] + inactivity_timer_value = 25 + inactivity_timer_probe = false + vlan = 27 + voice_vlan = false + linksec_policy = "must-secure" + sgt = 57 + absolute_timer = 45 + description = "service_template_desc" + interface_template = [ + { + name = "template1" + } + ] + tunnel_capwap_name = "tunnel_name" + vnid = "vnid_1" + redirect_append_client_mac = "00:01:00:01:00:01" + redirect_append_switch_mac = "00:01:00:01:00:02" + redirect_url_url_name = "valid_url" + redirect_url_match_acl_name = "acl_name" + redirect_url_match_action = "redirect-on-no-match" + dns_acl_preauth = "dns_acl_name" + service_policy_qos_input = "input_qos" + service_policy_qos_output = "output_qos" + tag_config = [ + { + name = "tag_name" + } + ] +} +``` + + +## Schema + +### Required + +- `word` (String) Specify a template name (maximum 48 characters) + +### Optional + +- `absolute_timer` (Number) Enter a value between 1 and 1073741823 + - Range: `1`-`1073741823` +- `access_group` (Attributes List) Access list to be applied (see [below for nested schema](#nestedatt--access_group)) +- `description` (String) Enter a description +- `device` (String) A device name from the provider configuration. +- `dns_acl_preauth` (String) pre-authentication +- `inactivity_timer_probe` (Boolean) ARP probe +- `inactivity_timer_value` (Number) Enter a value between 1 and 65535 + - Range: `1`-`65535` +- `interface_template` (Attributes List) Interface template to be applied (see [below for nested schema](#nestedatt--interface_template)) +- `linksec_policy` (String) Set the link security policy + - Choices: `must-not-secure`, `must-secure`, `should-secure` +- `mdns_service_policy` (String) mdns policy to be applied +- `redirect_append_client_mac` (String) Append client Mac Address in redirect URL +- `redirect_append_switch_mac` (String) Append switch Mac Address in redirect URL +- `redirect_url_match_acl_name` (String) Specify the access list name +- `redirect_url_match_action` (String) - Choices: `one-time-redirect`, `redirect-on-no-match` +- `redirect_url_url_name` (String) Specify a valid URL +- `service_policy_qos_input` (String) Configure input Qos policy +- `service_policy_qos_output` (String) Configure output Qos policy +- `sgt` (Number) SGT tag + - Range: `2`-`65519` +- `tag_config` (Attributes List) tag name (see [below for nested schema](#nestedatt--tag_config)) +- `tunnel_capwap_name` (String) tunnel profile name +- `vlan` (Number) Vlan to be applied + - Range: `1`-`4094` +- `vnid` (String) Vnid to be applied +- `voice_vlan` (Boolean) Critical voice vlan + +### Read-Only + +- `id` (String) The path of the object. + + +### Nested Schema for `access_group` + +Required: + +- `name` (String) Specify the access list name + + + +### Nested Schema for `interface_template` + +Required: + +- `name` (String) Enter name of interface template + + + +### Nested Schema for `tag_config` + +Required: + +- `name` (String) Specify the Tag name + +## Import + +Import is supported using the following syntax: + +```shell +terraform import iosxe_service_template.example "Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=DEFAULT_LINKSEC_POLICY_MUST_SECURE" +``` diff --git a/docs/resources/template.md b/docs/resources/template.md index 2845ad7c..694b7118 100644 --- a/docs/resources/template.md +++ b/docs/resources/template.md @@ -18,6 +18,8 @@ resource "iosxe_template" "example" { dot1x_pae = "both" dot1x_max_reauth_req = 3 dot1x_max_req = 3 + dot1x_timeout_tx_period = 2 + service_policy_subscriber = "dot1x_policy" service_policy_input = "SP1" service_policy_output = "SP2" switchport_mode_trunk = true @@ -78,14 +80,10 @@ resource "iosxe_template" "example" { access_list = "ACL1" } ] - subscriber_aging_probe = true - device_tracking = true - device_tracking_vlan_range = "100-199" - cts_manual = true - cts_manual_policy_static_sgt = 100 - cts_manual_policy_static_trusted = false - cts_manual_propagate_sgt = false - cts_role_based_enforcement = false + subscriber_aging_probe = true + device_tracking = true + device_tracking_vlan_range = "100-199" + cts_manual = true } ``` @@ -131,6 +129,8 @@ resource "iosxe_template" "example" { - Range: `1`-`10` - `dot1x_pae` (String) Set 802.1x interface pae type - Choices: `authenticator`, `both`, `supplicant` +- `dot1x_timeout_tx_period` (Number) Timeout for supplicant retries + - Range: `1`-`65535` - `ip_access_group` (Attributes List) Access control list for IP packets (see [below for nested schema](#nestedatt--ip_access_group)) - `ip_dhcp_snooping_limit_rate` (Number) DHCP snooping rate limit - Range: `1`-`2048` @@ -141,6 +141,7 @@ resource "iosxe_template" "example" { - `mab_eap` (Boolean) Use EAP authentication for MAC Auth Bypass - `service_policy_input` (String) policy-map name - `service_policy_output` (String) policy-map name +- `service_policy_subscriber` (String) Apply a subscriber control policy to the interface - `source_template` (String) Get config from a template - `spanning_tree_bpduguard_enable` (Boolean) Enable BPDU guard for this interface - `spanning_tree_portfast` (Boolean) Portfast options for the interface diff --git a/docs/resources/vlan_access_map.md b/docs/resources/vlan_access_map.md new file mode 100644 index 00000000..f9a708a6 --- /dev/null +++ b/docs/resources/vlan_access_map.md @@ -0,0 +1,52 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_vlan_access_map Resource - terraform-provider-iosxe" +subcategory: "Switching" +description: |- + This resource can manage the VLAN Access Map configuration. +--- + +# iosxe_vlan_access_map (Resource) + +This resource can manage the VLAN Access Map configuration. + +## Example Usage + +```terraform +resource "iosxe_vlan_access_map" "example" { + name = "accessmap1" + value = 1000 + match_ipv6_address = ["ipv6_address1"] + match_ip_address = ["ip_address1"] + action = "forward" +} +``` + + +## Schema + +### Required + +- `name` (String) Vlan access map tag +- `value` (Number) Sequence to insert to/delete from existing vlan access-map entry + - Range: `0`-`65535` + +### Optional + +- `action` (String) Take the action + - Choices: `drop`, `forward` +- `device` (String) A device name from the provider configuration. +- `match_ip_address` (List of String) +- `match_ipv6_address` (List of String) Match IPv6 address to access control. + +### Read-Only + +- `id` (String) The path of the object. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import iosxe_vlan_access_map.example "Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=accessmap1,1000" +``` diff --git a/docs/resources/vlan_configuration.md b/docs/resources/vlan_configuration.md index 00b83ea5..aade82e3 100644 --- a/docs/resources/vlan_configuration.md +++ b/docs/resources/vlan_configuration.md @@ -14,9 +14,7 @@ This resource can manage the VLAN Configuration configuration. ```terraform resource "iosxe_vlan_configuration" "example" { - vlan_id = 123 - evpn_instance = 123 - evpn_instance_vni = 10123 + vlan_id = 123 } ``` diff --git a/examples/data-sources/iosxe_service_template/data-source.tf b/examples/data-sources/iosxe_service_template/data-source.tf new file mode 100644 index 00000000..359a11c3 --- /dev/null +++ b/examples/data-sources/iosxe_service_template/data-source.tf @@ -0,0 +1,3 @@ +data "iosxe_service_template" "example" { + word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE" +} diff --git a/examples/data-sources/iosxe_vlan_access_map/data-source.tf b/examples/data-sources/iosxe_vlan_access_map/data-source.tf new file mode 100644 index 00000000..80999ea5 --- /dev/null +++ b/examples/data-sources/iosxe_vlan_access_map/data-source.tf @@ -0,0 +1,4 @@ +data "iosxe_vlan_access_map" "example" { + name = "accessmap1" + value = 1000 +} diff --git a/examples/resources/iosxe_aaa/resource.tf b/examples/resources/iosxe_aaa/resource.tf index 47a067d7..3f3d25c7 100644 --- a/examples/resources/iosxe_aaa/resource.tf +++ b/examples/resources/iosxe_aaa/resource.tf @@ -17,6 +17,7 @@ resource "iosxe_aaa" "example" { name = "TESTRADIUS" } ] + ip_radius_source_interface_loopback = 0 } ] group_tacacsplus = [ diff --git a/examples/resources/iosxe_aaa_authorization/resource.tf b/examples/resources/iosxe_aaa_authorization/resource.tf index fe49dc3c..43a5ef08 100644 --- a/examples/resources/iosxe_aaa_authorization/resource.tf +++ b/examples/resources/iosxe_aaa_authorization/resource.tf @@ -3,6 +3,7 @@ resource "iosxe_aaa_authorization" "example" { { name = "TEST" a1_local = false + a1_group = "false" a1_if_authenticated = true } ] diff --git a/examples/resources/iosxe_interface_tunnel/resource.tf b/examples/resources/iosxe_interface_tunnel/resource.tf index 11445bc9..cb82d690 100644 --- a/examples/resources/iosxe_interface_tunnel/resource.tf +++ b/examples/resources/iosxe_interface_tunnel/resource.tf @@ -24,6 +24,7 @@ resource "iosxe_interface_tunnel" "example" { ] tunnel_destination_ipv4 = "2.2.2.2" crypto_ipsec_df_bit = "clear" + arp_timeout = 300 ipv4_address = "10.1.1.1" ipv4_address_mask = "255.255.255.0" ip_dhcp_relay_source_interface = "Loopback100" diff --git a/examples/resources/iosxe_service_template/import.sh b/examples/resources/iosxe_service_template/import.sh new file mode 100644 index 00000000..09101ffa --- /dev/null +++ b/examples/resources/iosxe_service_template/import.sh @@ -0,0 +1 @@ +terraform import iosxe_service_template.example "Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=DEFAULT_LINKSEC_POLICY_MUST_SECURE" diff --git a/examples/resources/iosxe_service_template/resource.tf b/examples/resources/iosxe_service_template/resource.tf new file mode 100644 index 00000000..5a7c64b2 --- /dev/null +++ b/examples/resources/iosxe_service_template/resource.tf @@ -0,0 +1,36 @@ +resource "iosxe_service_template" "example" { + word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE" + access_group = [ + { + name = "ag1" + } + ] + inactivity_timer_value = 25 + inactivity_timer_probe = false + vlan = 27 + voice_vlan = false + linksec_policy = "must-secure" + sgt = 57 + absolute_timer = 45 + description = "service_template_desc" + interface_template = [ + { + name = "template1" + } + ] + tunnel_capwap_name = "tunnel_name" + vnid = "vnid_1" + redirect_append_client_mac = "00:01:00:01:00:01" + redirect_append_switch_mac = "00:01:00:01:00:02" + redirect_url_url_name = "valid_url" + redirect_url_match_acl_name = "acl_name" + redirect_url_match_action = "redirect-on-no-match" + dns_acl_preauth = "dns_acl_name" + service_policy_qos_input = "input_qos" + service_policy_qos_output = "output_qos" + tag_config = [ + { + name = "tag_name" + } + ] +} diff --git a/examples/resources/iosxe_template/resource.tf b/examples/resources/iosxe_template/resource.tf index 2f2fa5b5..56a6cd16 100644 --- a/examples/resources/iosxe_template/resource.tf +++ b/examples/resources/iosxe_template/resource.tf @@ -3,6 +3,8 @@ resource "iosxe_template" "example" { dot1x_pae = "both" dot1x_max_reauth_req = 3 dot1x_max_req = 3 + dot1x_timeout_tx_period = 2 + service_policy_subscriber = "dot1x_policy" service_policy_input = "SP1" service_policy_output = "SP2" switchport_mode_trunk = true @@ -63,12 +65,8 @@ resource "iosxe_template" "example" { access_list = "ACL1" } ] - subscriber_aging_probe = true - device_tracking = true - device_tracking_vlan_range = "100-199" - cts_manual = true - cts_manual_policy_static_sgt = 100 - cts_manual_policy_static_trusted = false - cts_manual_propagate_sgt = false - cts_role_based_enforcement = false + subscriber_aging_probe = true + device_tracking = true + device_tracking_vlan_range = "100-199" + cts_manual = true } diff --git a/examples/resources/iosxe_vlan_access_map/import.sh b/examples/resources/iosxe_vlan_access_map/import.sh new file mode 100644 index 00000000..3c5aa942 --- /dev/null +++ b/examples/resources/iosxe_vlan_access_map/import.sh @@ -0,0 +1 @@ +terraform import iosxe_vlan_access_map.example "Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=accessmap1,1000" diff --git a/examples/resources/iosxe_vlan_access_map/resource.tf b/examples/resources/iosxe_vlan_access_map/resource.tf new file mode 100644 index 00000000..b4efbc4f --- /dev/null +++ b/examples/resources/iosxe_vlan_access_map/resource.tf @@ -0,0 +1,7 @@ +resource "iosxe_vlan_access_map" "example" { + name = "accessmap1" + value = 1000 + match_ipv6_address = ["ipv6_address1"] + match_ip_address = ["ip_address1"] + action = "forward" +} diff --git a/examples/resources/iosxe_vlan_configuration/resource.tf b/examples/resources/iosxe_vlan_configuration/resource.tf index 31c31476..ef169796 100644 --- a/examples/resources/iosxe_vlan_configuration/resource.tf +++ b/examples/resources/iosxe_vlan_configuration/resource.tf @@ -1,5 +1,3 @@ resource "iosxe_vlan_configuration" "example" { - vlan_id = 123 - evpn_instance = 123 - evpn_instance_vni = 10123 + vlan_id = 123 } diff --git a/gen/definitions/aaa.yaml b/gen/definitions/aaa.yaml index 023206b9..dbf7d450 100644 --- a/gen/definitions/aaa.yaml +++ b/gen/definitions/aaa.yaml @@ -38,6 +38,11 @@ attributes: - yang_name: name example: TESTRADIUS id: true + # SP 11 + - yang_name: ip/radius/source-interface/interface-choice/Loopback/Loopback + xpath: ip/radius/source-interface/Loopback + tf_name: ip_radius_source_interface_loopback + example: 0 - yang_name: Cisco-IOS-XE-aaa:group/server/tacacsplus tf_name: group_tacacsplus type: List diff --git a/gen/definitions/aaa_authorization.yaml b/gen/definitions/aaa_authorization.yaml index 0a8882a8..60380619 100644 --- a/gen/definitions/aaa_authorization.yaml +++ b/gen/definitions/aaa_authorization.yaml @@ -15,6 +15,17 @@ attributes: xpath: a1/local tf_name: a1_local example: false + # SP 11 + - yang_name: a1/auth-exec-choice/group/group + xpath: a1/group + tf_name: a1_group + example: false + # SP 11 + - yang_name: a2/auth-exec-choice/local/local + xpath: a2/local + tf_name: a2_local + example: false + exclude_test: true - yang_name: a1/auth-exec-choice/if-authenticated/if-authenticated xpath: a1/if-authenticated tf_name: a1_if_authenticated diff --git a/gen/definitions/interface_tunnel.yaml b/gen/definitions/interface_tunnel.yaml index 63a1ca0d..2bd4a718 100644 --- a/gen/definitions/interface_tunnel.yaml +++ b/gen/definitions/interface_tunnel.yaml @@ -62,6 +62,9 @@ attributes: exclude_test: true - yang_name: Cisco-IOS-XE-crypto:crypto/ipsec/df-bit example: clear + # SP 11 + - yang_name: arp/timeout + example: 300 - yang_name: ip/address-choice/address/address/address-choice/fixed-case/primary/address xpath: ip/address/primary/address tf_name: ipv4_address diff --git a/gen/definitions/service_template.yaml b/gen/definitions/service_template.yaml new file mode 100644 index 00000000..88ee7745 --- /dev/null +++ b/gen/definitions/service_template.yaml @@ -0,0 +1,72 @@ +--- +name: Service Template +path: Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=%v +doc_category: System +no_delete_attributes: true +attributes: + - yang_name: word + example: DEFAULT_LINKSEC_POLICY_MUST_SECURE + - yang_name: access-group-config + type: List + tf_name: access_group + attributes: + - yang_name: name + id: true + example: ag1 + - yang_name: inactivity-timer/value + example: 25 + - yang_name: inactivity-timer/probe + example: false + - yang_name: vlan + example: 27 + - yang_name: voice/vlan + example: false + - yang_name: linksec/policy + example: must-secure + - yang_name: sgt + example: 57 + - yang_name: absolute-timer + example: 45 + - yang_name: description + example: service_template_desc + - yang_name: interface-template + tf_name: interface_template + type: List + attributes: + - yang_name: name + id: true + example: template1 + - yang_name: tunnel/type/capwap/name + tf_name: tunnel_capwap_name + example: tunnel_name + - yang_name: vnid + example: vnid_1 + - yang_name: redirect/append/client-mac + example: 00:01:00:01:00:01 + - yang_name: redirect/append/switch-mac + example: 00:01:00:01:00:02 + - yang_name: redirect/url/url_name + example: valid_url + - yang_name: redirect/url/match/acl_name + example: acl_name + - yang_name: redirect/url/match/action + example: redirect-on-no-match + - yang_name: dns-acl/preauth + example: dns_acl_name + - yang_name: service-policy/qos/input + example: input_qos + - yang_name: service-policy/qos/output + example: output_qos + - yang_name: tag-config + tf_name: tag_config + type: List + attributes: + - yang_name: name + id: true + example: tag_name + # not available on device + - yang_name: mdns-service-policy + example: MDNS_SERVICE_POLICY_NAME + exclude_test: true + + diff --git a/gen/definitions/template.yaml b/gen/definitions/template.yaml index 6349a3ad..cd45e588 100644 --- a/gen/definitions/template.yaml +++ b/gen/definitions/template.yaml @@ -12,6 +12,12 @@ attributes: example: 3 - yang_name: dot1x/max-req example: 3 + # SP 11 + - yang_name: dot1x/timeout/tx-period + example: 2 + - yang_name: service-policy/type/control/subscriber + tf_name: service_policy_subscriber + example: dot1x_policy - yang_name: service-policy/input/policy-map-name tf_name: service_policy_input example: SP1 @@ -195,9 +201,22 @@ attributes: example: true - yang_name: cts/manual/policy/static/sgt example: 100 + exclude_test: true - yang_name: cts/manual/policy/static/trusted example: false + exclude_test: true - yang_name: cts/manual/propagate/sgt - example: false + example: true + exclude_test: true - yang_name: cts/role-based/enforcement example: false + exclude_test: true +test_prerequisites: + - path: Cisco-IOS-XE-native:native/policy/Cisco-IOS-XE-policy:policy-map=dot1x_policy + attributes: + - name: name + value: dot1x_policy + - name: type + value: control + - name: subscriber + value: "" diff --git a/gen/definitions/vlan_access_map.yaml b/gen/definitions/vlan_access_map.yaml new file mode 100644 index 00000000..efbed13a --- /dev/null +++ b/gen/definitions/vlan_access_map.yaml @@ -0,0 +1,17 @@ +--- +name: VLAN Access Map +path: Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=%v,%s +no_delete_attributes: true +test_tags: [C9000V] +doc_category: Switching +attributes: + - yang_name: name + example: accessmap1 + - yang_name: value + example: 1000 + - yang_name: match/ipv6/address + example: "ipv6_address1" + - yang_name: match/ip/address + example: ip_address1 + - yang_name: action + example: forward diff --git a/gen/load_models.go b/gen/load_models.go index 25738b53..3a1f2f4b 100644 --- a/gen/load_models.go +++ b/gen/load_models.go @@ -93,6 +93,7 @@ var models = []string{ "https://raw.githubusercontent.com/YangModels/yang/main/vendor/cisco/xe/1791/Cisco-IOS-XE-dot1x.yang", "https://raw.githubusercontent.com/YangModels/yang/main/vendor/cisco/xe/1791/Cisco-IOS-XE-mdns-gateway.yang", "https://raw.githubusercontent.com/YangModels/yang/main/vendor/cisco/xe/1791/Cisco-IOS-XE-udld.yang", + "https://raw.githubusercontent.com/YangModels/yang/main/vendor/cisco/xe/1791/Cisco-IOS-XE-switch.yang", } const ( diff --git a/internal/provider/data_source_iosxe_aaa.go b/internal/provider/data_source_iosxe_aaa.go index 87db0faf..0460b2fa 100644 --- a/internal/provider/data_source_iosxe_aaa.go +++ b/internal/provider/data_source_iosxe_aaa.go @@ -116,6 +116,10 @@ func (d *AAADataSource) Schema(ctx context.Context, req datasource.SchemaRequest }, }, }, + "ip_radius_source_interface_loopback": schema.Int64Attribute{ + MarkdownDescription: "Loopback interface", + Computed: true, + }, }, }, }, diff --git a/internal/provider/data_source_iosxe_aaa_authorization.go b/internal/provider/data_source_iosxe_aaa_authorization.go index 03ba4b60..fbcf8034 100644 --- a/internal/provider/data_source_iosxe_aaa_authorization.go +++ b/internal/provider/data_source_iosxe_aaa_authorization.go @@ -76,6 +76,14 @@ func (d *AAAAuthorizationDataSource) Schema(ctx context.Context, req datasource. MarkdownDescription: "Use local database", Computed: true, }, + "a1_group": schema.StringAttribute{ + MarkdownDescription: "Use Server-group", + Computed: true, + }, + "a2_local": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, "a1_if_authenticated": schema.BoolAttribute{ MarkdownDescription: "Succeed if user has authenticated.", Computed: true, diff --git a/internal/provider/data_source_iosxe_aaa_authorization_test.go b/internal/provider/data_source_iosxe_aaa_authorization_test.go index 87884dee..d088af94 100644 --- a/internal/provider/data_source_iosxe_aaa_authorization_test.go +++ b/internal/provider/data_source_iosxe_aaa_authorization_test.go @@ -33,6 +33,7 @@ func TestAccDataSourceIosxeAAAAuthorization(t *testing.T) { var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa_authorization.test", "execs.0.name", "TEST")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa_authorization.test", "execs.0.a1_local", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa_authorization.test", "execs.0.a1_group", "false")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa_authorization.test", "execs.0.a1_if_authenticated", "true")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -52,6 +53,7 @@ func testAccDataSourceIosxeAAAAuthorizationConfig() string { config += ` execs = [{` + "\n" config += ` name = "TEST"` + "\n" config += ` a1_local = false` + "\n" + config += ` a1_group = "false"` + "\n" config += ` a1_if_authenticated = true` + "\n" config += ` }]` + "\n" config += `}` + "\n" diff --git a/internal/provider/data_source_iosxe_aaa_test.go b/internal/provider/data_source_iosxe_aaa_test.go index bc1eb850..0d2c8a58 100644 --- a/internal/provider/data_source_iosxe_aaa_test.go +++ b/internal/provider/data_source_iosxe_aaa_test.go @@ -39,6 +39,7 @@ func TestAccDataSourceIosxeAAA(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "server_radius_dynamic_author_clients.0.server_key", "abcd123")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "group_server_radius.0.name", "T-Group")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "group_server_radius.0.server_names.0.name", "TESTRADIUS")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "group_server_radius.0.ip_radius_source_interface_loopback", "0")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "group_tacacsplus.0.name", "tacacs-group")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_aaa.test", "group_tacacsplus.0.servers.0.name", "tacacs_10.10.15.12")) resource.Test(t, resource.TestCase{ @@ -68,6 +69,7 @@ func testAccDataSourceIosxeAAAConfig() string { config += ` server_names = [{` + "\n" config += ` name = "TESTRADIUS"` + "\n" config += ` }]` + "\n" + config += ` ip_radius_source_interface_loopback = 0` + "\n" config += ` }]` + "\n" config += ` group_tacacsplus = [{` + "\n" config += ` name = "tacacs-group"` + "\n" diff --git a/internal/provider/data_source_iosxe_interface_tunnel.go b/internal/provider/data_source_iosxe_interface_tunnel.go index 3654f366..8d983a25 100644 --- a/internal/provider/data_source_iosxe_interface_tunnel.go +++ b/internal/provider/data_source_iosxe_interface_tunnel.go @@ -159,6 +159,10 @@ func (d *InterfaceTunnelDataSource) Schema(ctx context.Context, req datasource.S MarkdownDescription: "Handling of encapsulated DF bit.", Computed: true, }, + "arp_timeout": schema.Int64Attribute{ + MarkdownDescription: "Set ARP cache timeout", + Computed: true, + }, "ipv4_address": schema.StringAttribute{ MarkdownDescription: "", Computed: true, diff --git a/internal/provider/data_source_iosxe_interface_tunnel_test.go b/internal/provider/data_source_iosxe_interface_tunnel_test.go index 642a80c3..1776d15a 100644 --- a/internal/provider/data_source_iosxe_interface_tunnel_test.go +++ b/internal/provider/data_source_iosxe_interface_tunnel_test.go @@ -44,6 +44,7 @@ func TestAccDataSourceIosxeInterfaceTunnel(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "ipv6_addresses.0.eui_64", "true")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "tunnel_destination_ipv4", "2.2.2.2")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "crypto_ipsec_df_bit", "clear")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "arp_timeout", "300")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "ipv4_address", "10.1.1.1")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "ipv4_address_mask", "255.255.255.0")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_interface_tunnel.test", "ip_dhcp_relay_source_interface", "Loopback100")) @@ -115,6 +116,7 @@ func testAccDataSourceIosxeInterfaceTunnelConfig() string { config += ` }]` + "\n" config += ` tunnel_destination_ipv4 = "2.2.2.2"` + "\n" config += ` crypto_ipsec_df_bit = "clear"` + "\n" + config += ` arp_timeout = 300` + "\n" config += ` ipv4_address = "10.1.1.1"` + "\n" config += ` ipv4_address_mask = "255.255.255.0"` + "\n" config += ` ip_dhcp_relay_source_interface = "Loopback100"` + "\n" diff --git a/internal/provider/data_source_iosxe_service_template.go b/internal/provider/data_source_iosxe_service_template.go new file mode 100644 index 00000000..121d1851 --- /dev/null +++ b/internal/provider/data_source_iosxe_service_template.go @@ -0,0 +1,229 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-restconf" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &ServiceTemplateDataSource{} + _ datasource.DataSourceWithConfigure = &ServiceTemplateDataSource{} +) + +func NewServiceTemplateDataSource() datasource.DataSource { + return &ServiceTemplateDataSource{} +} + +type ServiceTemplateDataSource struct { + clients map[string]*restconf.Client +} + +func (d *ServiceTemplateDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_template" +} + +func (d *ServiceTemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This data source can read the Service Template configuration.", + + Attributes: map[string]schema.Attribute{ + "device": schema.StringAttribute{ + MarkdownDescription: "A device name from the provider configuration.", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The path of the retrieved object.", + Computed: true, + }, + "word": schema.StringAttribute{ + MarkdownDescription: "Specify a template name (maximum 48 characters)", + Required: true, + }, + "access_group": schema.ListNestedAttribute{ + MarkdownDescription: "Access list to be applied", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Specify the access list name", + Computed: true, + }, + }, + }, + }, + "inactivity_timer_value": schema.Int64Attribute{ + MarkdownDescription: "Enter a value between 1 and 65535", + Computed: true, + }, + "inactivity_timer_probe": schema.BoolAttribute{ + MarkdownDescription: "ARP probe", + Computed: true, + }, + "vlan": schema.Int64Attribute{ + MarkdownDescription: "Vlan to be applied", + Computed: true, + }, + "voice_vlan": schema.BoolAttribute{ + MarkdownDescription: "Critical voice vlan", + Computed: true, + }, + "linksec_policy": schema.StringAttribute{ + MarkdownDescription: "Set the link security policy", + Computed: true, + }, + "sgt": schema.Int64Attribute{ + MarkdownDescription: "SGT tag", + Computed: true, + }, + "absolute_timer": schema.Int64Attribute{ + MarkdownDescription: "Enter a value between 1 and 1073741823", + Computed: true, + }, + "description": schema.StringAttribute{ + MarkdownDescription: "Enter a description", + Computed: true, + }, + "interface_template": schema.ListNestedAttribute{ + MarkdownDescription: "Interface template to be applied", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Enter name of interface template", + Computed: true, + }, + }, + }, + }, + "tunnel_capwap_name": schema.StringAttribute{ + MarkdownDescription: "tunnel profile name", + Computed: true, + }, + "vnid": schema.StringAttribute{ + MarkdownDescription: "Vnid to be applied", + Computed: true, + }, + "redirect_append_client_mac": schema.StringAttribute{ + MarkdownDescription: "Append client Mac Address in redirect URL", + Computed: true, + }, + "redirect_append_switch_mac": schema.StringAttribute{ + MarkdownDescription: "Append switch Mac Address in redirect URL", + Computed: true, + }, + "redirect_url_url_name": schema.StringAttribute{ + MarkdownDescription: "Specify a valid URL", + Computed: true, + }, + "redirect_url_match_acl_name": schema.StringAttribute{ + MarkdownDescription: "Specify the access list name", + Computed: true, + }, + "redirect_url_match_action": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "dns_acl_preauth": schema.StringAttribute{ + MarkdownDescription: "pre-authentication", + Computed: true, + }, + "service_policy_qos_input": schema.StringAttribute{ + MarkdownDescription: "Configure input Qos policy", + Computed: true, + }, + "service_policy_qos_output": schema.StringAttribute{ + MarkdownDescription: "Configure output Qos policy", + Computed: true, + }, + "tag_config": schema.ListNestedAttribute{ + MarkdownDescription: "tag name", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Specify the Tag name", + Computed: true, + }, + }, + }, + }, + "mdns_service_policy": schema.StringAttribute{ + MarkdownDescription: "mdns policy to be applied", + Computed: true, + }, + }, + } +} + +func (d *ServiceTemplateDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.clients = req.ProviderData.(map[string]*restconf.Client) +} + +func (d *ServiceTemplateDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config ServiceTemplateData + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := d.clients[config.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", config.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.getPath())) + + res, err := d.clients[config.Device.ValueString()].GetData(config.getPath()) + if res.StatusCode == 404 { + config = ServiceTemplateData{Device: config.Device} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + config.fromBody(ctx, res.Res) + } + + config.Id = types.StringValue(config.getPath()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.getPath())) + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/data_source_iosxe_service_template_test.go b/internal/provider/data_source_iosxe_service_template_test.go new file mode 100644 index 00000000..d787eed0 --- /dev/null +++ b/internal/provider/data_source_iosxe_service_template_test.go @@ -0,0 +1,102 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataSourceIosxeServiceTemplate(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "access_group.0.name", "ag1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "inactivity_timer_value", "25")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "inactivity_timer_probe", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "vlan", "27")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "voice_vlan", "false")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "linksec_policy", "must-secure")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "sgt", "57")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "absolute_timer", "45")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "description", "service_template_desc")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "interface_template.0.name", "template1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "tunnel_capwap_name", "tunnel_name")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "vnid", "vnid_1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "redirect_append_client_mac", "00:01:00:01:00:01")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "redirect_append_switch_mac", "00:01:00:01:00:02")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "redirect_url_url_name", "valid_url")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "redirect_url_match_acl_name", "acl_name")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "redirect_url_match_action", "redirect-on-no-match")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "dns_acl_preauth", "dns_acl_name")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "service_policy_qos_input", "input_qos")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "service_policy_qos_output", "output_qos")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_service_template.test", "tag_config.0.name", "tag_name")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceIosxeServiceTemplateConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +func testAccDataSourceIosxeServiceTemplateConfig() string { + config := `resource "iosxe_service_template" "test" {` + "\n" + config += ` word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE"` + "\n" + config += ` access_group = [{` + "\n" + config += ` name = "ag1"` + "\n" + config += ` }]` + "\n" + config += ` inactivity_timer_value = 25` + "\n" + config += ` inactivity_timer_probe = false` + "\n" + config += ` vlan = 27` + "\n" + config += ` voice_vlan = false` + "\n" + config += ` linksec_policy = "must-secure"` + "\n" + config += ` sgt = 57` + "\n" + config += ` absolute_timer = 45` + "\n" + config += ` description = "service_template_desc"` + "\n" + config += ` interface_template = [{` + "\n" + config += ` name = "template1"` + "\n" + config += ` }]` + "\n" + config += ` tunnel_capwap_name = "tunnel_name"` + "\n" + config += ` vnid = "vnid_1"` + "\n" + config += ` redirect_append_client_mac = "00:01:00:01:00:01"` + "\n" + config += ` redirect_append_switch_mac = "00:01:00:01:00:02"` + "\n" + config += ` redirect_url_url_name = "valid_url"` + "\n" + config += ` redirect_url_match_acl_name = "acl_name"` + "\n" + config += ` redirect_url_match_action = "redirect-on-no-match"` + "\n" + config += ` dns_acl_preauth = "dns_acl_name"` + "\n" + config += ` service_policy_qos_input = "input_qos"` + "\n" + config += ` service_policy_qos_output = "output_qos"` + "\n" + config += ` tag_config = [{` + "\n" + config += ` name = "tag_name"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + + config += ` + data "iosxe_service_template" "test" { + word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE" + depends_on = [iosxe_service_template.test] + } + ` + return config +} diff --git a/internal/provider/data_source_iosxe_template.go b/internal/provider/data_source_iosxe_template.go index 1e1aea18..24d5049d 100644 --- a/internal/provider/data_source_iosxe_template.go +++ b/internal/provider/data_source_iosxe_template.go @@ -79,6 +79,14 @@ func (d *TemplateDataSource) Schema(ctx context.Context, req datasource.SchemaRe MarkdownDescription: "Max No. of Retries", Computed: true, }, + "dot1x_timeout_tx_period": schema.Int64Attribute{ + MarkdownDescription: "Timeout for supplicant retries", + Computed: true, + }, + "service_policy_subscriber": schema.StringAttribute{ + MarkdownDescription: "Apply a subscriber control policy to the interface", + Computed: true, + }, "service_policy_input": schema.StringAttribute{ MarkdownDescription: "policy-map name", Computed: true, diff --git a/internal/provider/data_source_iosxe_template_test.go b/internal/provider/data_source_iosxe_template_test.go index 248b1443..79e155e7 100644 --- a/internal/provider/data_source_iosxe_template_test.go +++ b/internal/provider/data_source_iosxe_template_test.go @@ -34,6 +34,8 @@ func TestAccDataSourceIosxeTemplate(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "dot1x_pae", "both")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "dot1x_max_reauth_req", "3")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "dot1x_max_req", "3")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "dot1x_timeout_tx_period", "2")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "service_policy_subscriber", "dot1x_policy")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "service_policy_input", "SP1")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "service_policy_output", "SP2")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "switchport_mode_trunk", "true")) @@ -90,22 +92,30 @@ func TestAccDataSourceIosxeTemplate(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "device_tracking", "true")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "device_tracking_vlan_range", "100-199")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "cts_manual", "true")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "cts_manual_policy_static_sgt", "100")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "cts_manual_policy_static_trusted", "false")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "cts_manual_propagate_sgt", "false")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_template.test", "cts_role_based_enforcement", "false")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccDataSourceIosxeTemplateConfig(), + Config: testAccDataSourceIosxeTemplatePrerequisitesConfig + testAccDataSourceIosxeTemplateConfig(), Check: resource.ComposeTestCheckFunc(checks...), }, }, }) } +const testAccDataSourceIosxeTemplatePrerequisitesConfig = ` +resource "iosxe_restconf" "PreReq0" { + path = "Cisco-IOS-XE-native:native/policy/Cisco-IOS-XE-policy:policy-map=dot1x_policy" + attributes = { + "name" = "dot1x_policy" + "type" = "control" + "subscriber" = "" + } +} + +` + func testAccDataSourceIosxeTemplateConfig() string { config := `resource "iosxe_template" "test" {` + "\n" config += ` delete_mode = "attributes"` + "\n" @@ -113,6 +123,8 @@ func testAccDataSourceIosxeTemplateConfig() string { config += ` dot1x_pae = "both"` + "\n" config += ` dot1x_max_reauth_req = 3` + "\n" config += ` dot1x_max_req = 3` + "\n" + config += ` dot1x_timeout_tx_period = 2` + "\n" + config += ` service_policy_subscriber = "dot1x_policy"` + "\n" config += ` service_policy_input = "SP1"` + "\n" config += ` service_policy_output = "SP2"` + "\n" config += ` switchport_mode_trunk = true` + "\n" @@ -173,10 +185,7 @@ func testAccDataSourceIosxeTemplateConfig() string { config += ` device_tracking = true` + "\n" config += ` device_tracking_vlan_range = "100-199"` + "\n" config += ` cts_manual = true` + "\n" - config += ` cts_manual_policy_static_sgt = 100` + "\n" - config += ` cts_manual_policy_static_trusted = false` + "\n" - config += ` cts_manual_propagate_sgt = false` + "\n" - config += ` cts_role_based_enforcement = false` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, ]` + "\n" config += `}` + "\n" config += ` diff --git a/internal/provider/data_source_iosxe_vlan_access_map.go b/internal/provider/data_source_iosxe_vlan_access_map.go new file mode 100644 index 00000000..0499decd --- /dev/null +++ b/internal/provider/data_source_iosxe_vlan_access_map.go @@ -0,0 +1,135 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-restconf" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &VLANAccessMapDataSource{} + _ datasource.DataSourceWithConfigure = &VLANAccessMapDataSource{} +) + +func NewVLANAccessMapDataSource() datasource.DataSource { + return &VLANAccessMapDataSource{} +} + +type VLANAccessMapDataSource struct { + clients map[string]*restconf.Client +} + +func (d *VLANAccessMapDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_vlan_access_map" +} + +func (d *VLANAccessMapDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This data source can read the VLAN Access Map configuration.", + + Attributes: map[string]schema.Attribute{ + "device": schema.StringAttribute{ + MarkdownDescription: "A device name from the provider configuration.", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The path of the retrieved object.", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Vlan access map tag", + Required: true, + }, + "value": schema.Int64Attribute{ + MarkdownDescription: "Sequence to insert to/delete from existing vlan access-map entry", + Required: true, + }, + "match_ipv6_address": schema.ListAttribute{ + MarkdownDescription: "Match IPv6 address to access control.", + ElementType: types.StringType, + Computed: true, + }, + "match_ip_address": schema.ListAttribute{ + MarkdownDescription: "", + ElementType: types.StringType, + Computed: true, + }, + "action": schema.StringAttribute{ + MarkdownDescription: "Take the action", + Computed: true, + }, + }, + } +} + +func (d *VLANAccessMapDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.clients = req.ProviderData.(map[string]*restconf.Client) +} + +func (d *VLANAccessMapDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config VLANAccessMapData + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := d.clients[config.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", config.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.getPath())) + + res, err := d.clients[config.Device.ValueString()].GetData(config.getPath()) + if res.StatusCode == 404 { + config = VLANAccessMapData{Device: config.Device} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + config.fromBody(ctx, res.Res) + } + + config.Id = types.StringValue(config.getPath()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", config.getPath())) + + diags = resp.State.Set(ctx, &config) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/provider/data_source_iosxe_vlan_access_map_test.go b/internal/provider/data_source_iosxe_vlan_access_map_test.go new file mode 100644 index 00000000..0474e4df --- /dev/null +++ b/internal/provider/data_source_iosxe_vlan_access_map_test.go @@ -0,0 +1,66 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataSourceIosxeVLANAccessMap(t *testing.T) { + if os.Getenv("C9000V") == "" { + t.Skip("skipping test, set environment variable C9000V") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_vlan_access_map.test", "match_ipv6_address.0", "ipv6_address1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_vlan_access_map.test", "match_ip_address.0", "ip_address1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_vlan_access_map.test", "action", "forward")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceIosxeVLANAccessMapConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +func testAccDataSourceIosxeVLANAccessMapConfig() string { + config := `resource "iosxe_vlan_access_map" "test" {` + "\n" + config += ` name = "accessmap1"` + "\n" + config += ` value = 1000` + "\n" + config += ` match_ipv6_address = ["ipv6_address1"]` + "\n" + config += ` match_ip_address = ["ip_address1"]` + "\n" + config += ` action = "forward"` + "\n" + config += `}` + "\n" + + config += ` + data "iosxe_vlan_access_map" "test" { + name = "accessmap1" + value = 1000 + depends_on = [iosxe_vlan_access_map.test] + } + ` + return config +} diff --git a/internal/provider/data_source_iosxe_vlan_configuration_test.go b/internal/provider/data_source_iosxe_vlan_configuration_test.go index 1fb9347a..34d95a84 100644 --- a/internal/provider/data_source_iosxe_vlan_configuration_test.go +++ b/internal/provider/data_source_iosxe_vlan_configuration_test.go @@ -31,8 +31,6 @@ func TestAccDataSourceIosxeVLANConfiguration(t *testing.T) { t.Skip("skipping test, set environment variable C9000V") } var checks []resource.TestCheckFunc - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_vlan_configuration.test", "evpn_instance", "123")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_vlan_configuration.test", "evpn_instance_vni", "10123")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -48,8 +46,6 @@ func TestAccDataSourceIosxeVLANConfiguration(t *testing.T) { func testAccDataSourceIosxeVLANConfigurationConfig() string { config := `resource "iosxe_vlan_configuration" "test" {` + "\n" config += ` vlan_id = 123` + "\n" - config += ` evpn_instance = 123` + "\n" - config += ` evpn_instance_vni = 10123` + "\n" config += `}` + "\n" config += ` diff --git a/internal/provider/model_iosxe_aaa.go b/internal/provider/model_iosxe_aaa.go index bd93f3a1..3e088134 100644 --- a/internal/provider/model_iosxe_aaa.go +++ b/internal/provider/model_iosxe_aaa.go @@ -60,8 +60,9 @@ type AAAServerRadiusDynamicAuthorClients struct { ServerKey types.String `tfsdk:"server_key"` } type AAAGroupServerRadius struct { - Name types.String `tfsdk:"name"` - ServerNames []AAAGroupServerRadiusServerNames `tfsdk:"server_names"` + Name types.String `tfsdk:"name"` + ServerNames []AAAGroupServerRadiusServerNames `tfsdk:"server_names"` + IpRadiusSourceInterfaceLoopback types.Int64 `tfsdk:"ip_radius_source_interface_loopback"` } type AAAGroupTacacsplus struct { Name types.String `tfsdk:"name"` @@ -128,6 +129,9 @@ func (data AAA) toBody(ctx context.Context) string { if !item.Name.IsNull() && !item.Name.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"Cisco-IOS-XE-aaa:group.server.radius"+"."+strconv.Itoa(index)+"."+"name", item.Name.ValueString()) } + if !item.IpRadiusSourceInterfaceLoopback.IsNull() && !item.IpRadiusSourceInterfaceLoopback.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"Cisco-IOS-XE-aaa:group.server.radius"+"."+strconv.Itoa(index)+"."+"ip.radius.source-interface.Loopback", strconv.FormatInt(item.IpRadiusSourceInterfaceLoopback.ValueInt64(), 10)) + } if len(item.ServerNames) > 0 { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"Cisco-IOS-XE-aaa:group.server.radius"+"."+strconv.Itoa(index)+"."+"server.name", []interface{}{}) for cindex, citem := range item.ServerNames { @@ -281,6 +285,11 @@ func (data *AAA) updateFromBody(ctx context.Context, res gjson.Result) { data.GroupServerRadius[i].ServerNames[ci].Name = types.StringNull() } } + if value := r.Get("ip.radius.source-interface.Loopback"); value.Exists() && !data.GroupServerRadius[i].IpRadiusSourceInterfaceLoopback.IsNull() { + data.GroupServerRadius[i].IpRadiusSourceInterfaceLoopback = types.Int64Value(value.Int()) + } else { + data.GroupServerRadius[i].IpRadiusSourceInterfaceLoopback = types.Int64Null() + } } for i := range data.GroupTacacsplus { keys := [...]string{"name"} @@ -395,6 +404,9 @@ func (data *AAAData) fromBody(ctx context.Context, res gjson.Result) { return true }) } + if cValue := v.Get("ip.radius.source-interface.Loopback"); cValue.Exists() { + item.IpRadiusSourceInterfaceLoopback = types.Int64Value(cValue.Int()) + } data.GroupServerRadius = append(data.GroupServerRadius, item) return true }) diff --git a/internal/provider/model_iosxe_aaa_authorization.go b/internal/provider/model_iosxe_aaa_authorization.go index a532d457..109de9c4 100644 --- a/internal/provider/model_iosxe_aaa_authorization.go +++ b/internal/provider/model_iosxe_aaa_authorization.go @@ -50,6 +50,8 @@ type AAAAuthorizationData struct { type AAAAuthorizationExecs struct { Name types.String `tfsdk:"name"` A1Local types.Bool `tfsdk:"a1_local"` + A1Group types.String `tfsdk:"a1_group"` + A2Local types.Bool `tfsdk:"a2_local"` A1IfAuthenticated types.Bool `tfsdk:"a1_if_authenticated"` } type AAAAuthorizationNetworks struct { @@ -89,6 +91,14 @@ func (data AAAAuthorization) toBody(ctx context.Context) string { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"exec"+"."+strconv.Itoa(index)+"."+"a1.local", map[string]string{}) } } + if !item.A1Group.IsNull() && !item.A1Group.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"exec"+"."+strconv.Itoa(index)+"."+"a1.group", item.A1Group.ValueString()) + } + if !item.A2Local.IsNull() && !item.A2Local.IsUnknown() { + if item.A2Local.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"exec"+"."+strconv.Itoa(index)+"."+"a2.local", map[string]string{}) + } + } if !item.A1IfAuthenticated.IsNull() && !item.A1IfAuthenticated.IsUnknown() { if item.A1IfAuthenticated.ValueBool() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"exec"+"."+strconv.Itoa(index)+"."+"a1.if-authenticated", map[string]string{}) @@ -152,6 +162,20 @@ func (data *AAAAuthorization) updateFromBody(ctx context.Context, res gjson.Resu } else { data.Execs[i].A1Local = types.BoolNull() } + if value := r.Get("a1.group"); value.Exists() && !data.Execs[i].A1Group.IsNull() { + data.Execs[i].A1Group = types.StringValue(value.String()) + } else { + data.Execs[i].A1Group = types.StringNull() + } + if value := r.Get("a2.local"); !data.Execs[i].A2Local.IsNull() { + if value.Exists() { + data.Execs[i].A2Local = types.BoolValue(true) + } else { + data.Execs[i].A2Local = types.BoolValue(false) + } + } else { + data.Execs[i].A2Local = types.BoolNull() + } if value := r.Get("a1.if-authenticated"); !data.Execs[i].A1IfAuthenticated.IsNull() { if value.Exists() { data.Execs[i].A1IfAuthenticated = types.BoolValue(true) @@ -215,6 +239,14 @@ func (data *AAAAuthorizationData) fromBody(ctx context.Context, res gjson.Result } else { item.A1Local = types.BoolValue(false) } + if cValue := v.Get("a1.group"); cValue.Exists() { + item.A1Group = types.StringValue(cValue.String()) + } + if cValue := v.Get("a2.local"); cValue.Exists() { + item.A2Local = types.BoolValue(true) + } else { + item.A2Local = types.BoolValue(false) + } if cValue := v.Get("a1.if-authenticated"); cValue.Exists() { item.A1IfAuthenticated = types.BoolValue(true) } else { @@ -312,6 +344,9 @@ func (data *AAAAuthorization) getEmptyLeafsDelete(ctx context.Context) []string if !data.Execs[i].A1Local.IsNull() && !data.Execs[i].A1Local.ValueBool() { emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/exec=%v/a1/local", data.getPath(), strings.Join(keyValues[:], ","))) } + if !data.Execs[i].A2Local.IsNull() && !data.Execs[i].A2Local.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/exec=%v/a2/local", data.getPath(), strings.Join(keyValues[:], ","))) + } if !data.Execs[i].A1IfAuthenticated.IsNull() && !data.Execs[i].A1IfAuthenticated.ValueBool() { emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/exec=%v/a1/if-authenticated", data.getPath(), strings.Join(keyValues[:], ","))) } diff --git a/internal/provider/model_iosxe_interface_tunnel.go b/internal/provider/model_iosxe_interface_tunnel.go index 75f8f391..2e17e4b1 100644 --- a/internal/provider/model_iosxe_interface_tunnel.go +++ b/internal/provider/model_iosxe_interface_tunnel.go @@ -56,6 +56,7 @@ type InterfaceTunnel struct { TunnelDestinationIpv4 types.String `tfsdk:"tunnel_destination_ipv4"` TunnelProtectionIpsecProfile types.String `tfsdk:"tunnel_protection_ipsec_profile"` CryptoIpsecDfBit types.String `tfsdk:"crypto_ipsec_df_bit"` + ArpTimeout types.Int64 `tfsdk:"arp_timeout"` Ipv4Address types.String `tfsdk:"ipv4_address"` Ipv4AddressMask types.String `tfsdk:"ipv4_address_mask"` Unnumbered types.String `tfsdk:"unnumbered"` @@ -96,6 +97,7 @@ type InterfaceTunnelData struct { TunnelDestinationIpv4 types.String `tfsdk:"tunnel_destination_ipv4"` TunnelProtectionIpsecProfile types.String `tfsdk:"tunnel_protection_ipsec_profile"` CryptoIpsecDfBit types.String `tfsdk:"crypto_ipsec_df_bit"` + ArpTimeout types.Int64 `tfsdk:"arp_timeout"` Ipv4Address types.String `tfsdk:"ipv4_address"` Ipv4AddressMask types.String `tfsdk:"ipv4_address_mask"` Unnumbered types.String `tfsdk:"unnumbered"` @@ -207,6 +209,9 @@ func (data InterfaceTunnel) toBody(ctx context.Context) string { if !data.CryptoIpsecDfBit.IsNull() && !data.CryptoIpsecDfBit.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"Cisco-IOS-XE-crypto:crypto.ipsec.df-bit", data.CryptoIpsecDfBit.ValueString()) } + if !data.ArpTimeout.IsNull() && !data.ArpTimeout.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"arp.timeout", strconv.FormatInt(data.ArpTimeout.ValueInt64(), 10)) + } if !data.Ipv4Address.IsNull() && !data.Ipv4Address.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"ip.address.primary.address", data.Ipv4Address.ValueString()) } @@ -493,6 +498,11 @@ func (data *InterfaceTunnel) updateFromBody(ctx context.Context, res gjson.Resul } else { data.CryptoIpsecDfBit = types.StringNull() } + if value := res.Get(prefix + "arp.timeout"); value.Exists() && !data.ArpTimeout.IsNull() { + data.ArpTimeout = types.Int64Value(value.Int()) + } else { + data.ArpTimeout = types.Int64Null() + } if value := res.Get(prefix + "ip.address.primary.address"); value.Exists() && !data.Ipv4Address.IsNull() { data.Ipv4Address = types.StringValue(value.String()) } else { @@ -732,6 +742,9 @@ func (data *InterfaceTunnelData) fromBody(ctx context.Context, res gjson.Result) if value := res.Get(prefix + "Cisco-IOS-XE-crypto:crypto.ipsec.df-bit"); value.Exists() { data.CryptoIpsecDfBit = types.StringValue(value.String()) } + if value := res.Get(prefix + "arp.timeout"); value.Exists() { + data.ArpTimeout = types.Int64Value(value.Int()) + } if value := res.Get(prefix + "ip.address.primary.address"); value.Exists() { data.Ipv4Address = types.StringValue(value.String()) } @@ -1103,6 +1116,9 @@ func (data *InterfaceTunnel) getDeletePaths(ctx context.Context) []string { if !data.CryptoIpsecDfBit.IsNull() { deletePaths = append(deletePaths, fmt.Sprintf("%v/Cisco-IOS-XE-crypto:crypto/ipsec/df-bit", data.getPath())) } + if !data.ArpTimeout.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/arp/timeout", data.getPath())) + } if !data.Ipv4Address.IsNull() { deletePaths = append(deletePaths, fmt.Sprintf("%v/ip/address/primary", data.getPath())) } diff --git a/internal/provider/model_iosxe_service_template.go b/internal/provider/model_iosxe_service_template.go new file mode 100644 index 00000000..fb578815 --- /dev/null +++ b/internal/provider/model_iosxe_service_template.go @@ -0,0 +1,685 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + "net/url" + "reflect" + "regexp" + "strconv" + "strings" + + "github.com/CiscoDevNet/terraform-provider-iosxe/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +type ServiceTemplate struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + Word types.String `tfsdk:"word"` + AccessGroup []ServiceTemplateAccessGroup `tfsdk:"access_group"` + InactivityTimerValue types.Int64 `tfsdk:"inactivity_timer_value"` + InactivityTimerProbe types.Bool `tfsdk:"inactivity_timer_probe"` + Vlan types.Int64 `tfsdk:"vlan"` + VoiceVlan types.Bool `tfsdk:"voice_vlan"` + LinksecPolicy types.String `tfsdk:"linksec_policy"` + Sgt types.Int64 `tfsdk:"sgt"` + AbsoluteTimer types.Int64 `tfsdk:"absolute_timer"` + Description types.String `tfsdk:"description"` + InterfaceTemplate []ServiceTemplateInterfaceTemplate `tfsdk:"interface_template"` + TunnelCapwapName types.String `tfsdk:"tunnel_capwap_name"` + Vnid types.String `tfsdk:"vnid"` + RedirectAppendClientMac types.String `tfsdk:"redirect_append_client_mac"` + RedirectAppendSwitchMac types.String `tfsdk:"redirect_append_switch_mac"` + RedirectUrlUrlName types.String `tfsdk:"redirect_url_url_name"` + RedirectUrlMatchAclName types.String `tfsdk:"redirect_url_match_acl_name"` + RedirectUrlMatchAction types.String `tfsdk:"redirect_url_match_action"` + DnsAclPreauth types.String `tfsdk:"dns_acl_preauth"` + ServicePolicyQosInput types.String `tfsdk:"service_policy_qos_input"` + ServicePolicyQosOutput types.String `tfsdk:"service_policy_qos_output"` + TagConfig []ServiceTemplateTagConfig `tfsdk:"tag_config"` + MdnsServicePolicy types.String `tfsdk:"mdns_service_policy"` +} + +type ServiceTemplateData struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + Word types.String `tfsdk:"word"` + AccessGroup []ServiceTemplateAccessGroup `tfsdk:"access_group"` + InactivityTimerValue types.Int64 `tfsdk:"inactivity_timer_value"` + InactivityTimerProbe types.Bool `tfsdk:"inactivity_timer_probe"` + Vlan types.Int64 `tfsdk:"vlan"` + VoiceVlan types.Bool `tfsdk:"voice_vlan"` + LinksecPolicy types.String `tfsdk:"linksec_policy"` + Sgt types.Int64 `tfsdk:"sgt"` + AbsoluteTimer types.Int64 `tfsdk:"absolute_timer"` + Description types.String `tfsdk:"description"` + InterfaceTemplate []ServiceTemplateInterfaceTemplate `tfsdk:"interface_template"` + TunnelCapwapName types.String `tfsdk:"tunnel_capwap_name"` + Vnid types.String `tfsdk:"vnid"` + RedirectAppendClientMac types.String `tfsdk:"redirect_append_client_mac"` + RedirectAppendSwitchMac types.String `tfsdk:"redirect_append_switch_mac"` + RedirectUrlUrlName types.String `tfsdk:"redirect_url_url_name"` + RedirectUrlMatchAclName types.String `tfsdk:"redirect_url_match_acl_name"` + RedirectUrlMatchAction types.String `tfsdk:"redirect_url_match_action"` + DnsAclPreauth types.String `tfsdk:"dns_acl_preauth"` + ServicePolicyQosInput types.String `tfsdk:"service_policy_qos_input"` + ServicePolicyQosOutput types.String `tfsdk:"service_policy_qos_output"` + TagConfig []ServiceTemplateTagConfig `tfsdk:"tag_config"` + MdnsServicePolicy types.String `tfsdk:"mdns_service_policy"` +} +type ServiceTemplateAccessGroup struct { + Name types.String `tfsdk:"name"` +} +type ServiceTemplateInterfaceTemplate struct { + Name types.String `tfsdk:"name"` +} +type ServiceTemplateTagConfig struct { + Name types.String `tfsdk:"name"` +} + +func (data ServiceTemplate) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=%v", url.QueryEscape(fmt.Sprintf("%v", data.Word.ValueString()))) +} + +func (data ServiceTemplateData) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=%v", url.QueryEscape(fmt.Sprintf("%v", data.Word.ValueString()))) +} + +// if last path element has a key -> remove it +func (data ServiceTemplate) getPathShort() string { + path := data.getPath() + re := regexp.MustCompile(`(.*)=[^\/]*$`) + matches := re.FindStringSubmatch(path) + if len(matches) <= 1 { + return path + } + return matches[1] +} + +func (data ServiceTemplate) toBody(ctx context.Context) string { + body := `{"` + helpers.LastElement(data.getPath()) + `":{}}` + if !data.Word.IsNull() && !data.Word.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"word", data.Word.ValueString()) + } + if !data.InactivityTimerValue.IsNull() && !data.InactivityTimerValue.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"inactivity-timer.value", strconv.FormatInt(data.InactivityTimerValue.ValueInt64(), 10)) + } + if !data.InactivityTimerProbe.IsNull() && !data.InactivityTimerProbe.IsUnknown() { + if data.InactivityTimerProbe.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"inactivity-timer.probe", map[string]string{}) + } + } + if !data.Vlan.IsNull() && !data.Vlan.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"vlan", strconv.FormatInt(data.Vlan.ValueInt64(), 10)) + } + if !data.VoiceVlan.IsNull() && !data.VoiceVlan.IsUnknown() { + if data.VoiceVlan.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"voice.vlan", map[string]string{}) + } + } + if !data.LinksecPolicy.IsNull() && !data.LinksecPolicy.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"linksec.policy", data.LinksecPolicy.ValueString()) + } + if !data.Sgt.IsNull() && !data.Sgt.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"sgt", strconv.FormatInt(data.Sgt.ValueInt64(), 10)) + } + if !data.AbsoluteTimer.IsNull() && !data.AbsoluteTimer.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"absolute-timer", strconv.FormatInt(data.AbsoluteTimer.ValueInt64(), 10)) + } + if !data.Description.IsNull() && !data.Description.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"description", data.Description.ValueString()) + } + if !data.TunnelCapwapName.IsNull() && !data.TunnelCapwapName.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"tunnel.type.capwap.name", data.TunnelCapwapName.ValueString()) + } + if !data.Vnid.IsNull() && !data.Vnid.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"vnid", data.Vnid.ValueString()) + } + if !data.RedirectAppendClientMac.IsNull() && !data.RedirectAppendClientMac.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"redirect.append.client-mac", data.RedirectAppendClientMac.ValueString()) + } + if !data.RedirectAppendSwitchMac.IsNull() && !data.RedirectAppendSwitchMac.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"redirect.append.switch-mac", data.RedirectAppendSwitchMac.ValueString()) + } + if !data.RedirectUrlUrlName.IsNull() && !data.RedirectUrlUrlName.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"redirect.url.url_name", data.RedirectUrlUrlName.ValueString()) + } + if !data.RedirectUrlMatchAclName.IsNull() && !data.RedirectUrlMatchAclName.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"redirect.url.match.acl_name", data.RedirectUrlMatchAclName.ValueString()) + } + if !data.RedirectUrlMatchAction.IsNull() && !data.RedirectUrlMatchAction.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"redirect.url.match.action", data.RedirectUrlMatchAction.ValueString()) + } + if !data.DnsAclPreauth.IsNull() && !data.DnsAclPreauth.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"dns-acl.preauth", data.DnsAclPreauth.ValueString()) + } + if !data.ServicePolicyQosInput.IsNull() && !data.ServicePolicyQosInput.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"service-policy.qos.input", data.ServicePolicyQosInput.ValueString()) + } + if !data.ServicePolicyQosOutput.IsNull() && !data.ServicePolicyQosOutput.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"service-policy.qos.output", data.ServicePolicyQosOutput.ValueString()) + } + if !data.MdnsServicePolicy.IsNull() && !data.MdnsServicePolicy.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"mdns-service-policy", data.MdnsServicePolicy.ValueString()) + } + if len(data.AccessGroup) > 0 { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"access-group-config", []interface{}{}) + for index, item := range data.AccessGroup { + if !item.Name.IsNull() && !item.Name.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"access-group-config"+"."+strconv.Itoa(index)+"."+"name", item.Name.ValueString()) + } + } + } + if len(data.InterfaceTemplate) > 0 { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"interface-template", []interface{}{}) + for index, item := range data.InterfaceTemplate { + if !item.Name.IsNull() && !item.Name.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"interface-template"+"."+strconv.Itoa(index)+"."+"name", item.Name.ValueString()) + } + } + } + if len(data.TagConfig) > 0 { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"tag-config", []interface{}{}) + for index, item := range data.TagConfig { + if !item.Name.IsNull() && !item.Name.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"tag-config"+"."+strconv.Itoa(index)+"."+"name", item.Name.ValueString()) + } + } + } + return body +} + +func (data *ServiceTemplate) updateFromBody(ctx context.Context, res gjson.Result) { + prefix := helpers.LastElement(data.getPath()) + "." + if res.Get(helpers.LastElement(data.getPath())).IsArray() { + prefix += "0." + } + if value := res.Get(prefix + "word"); value.Exists() && !data.Word.IsNull() { + data.Word = types.StringValue(value.String()) + } else { + data.Word = types.StringNull() + } + for i := range data.AccessGroup { + keys := [...]string{"name"} + keyValues := [...]string{data.AccessGroup[i].Name.ValueString()} + + var r gjson.Result + res.Get(prefix + "access-group-config").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("name"); value.Exists() && !data.AccessGroup[i].Name.IsNull() { + data.AccessGroup[i].Name = types.StringValue(value.String()) + } else { + data.AccessGroup[i].Name = types.StringNull() + } + } + if value := res.Get(prefix + "inactivity-timer.value"); value.Exists() && !data.InactivityTimerValue.IsNull() { + data.InactivityTimerValue = types.Int64Value(value.Int()) + } else { + data.InactivityTimerValue = types.Int64Null() + } + if value := res.Get(prefix + "inactivity-timer.probe"); !data.InactivityTimerProbe.IsNull() { + if value.Exists() { + data.InactivityTimerProbe = types.BoolValue(true) + } else { + data.InactivityTimerProbe = types.BoolValue(false) + } + } else { + data.InactivityTimerProbe = types.BoolNull() + } + if value := res.Get(prefix + "vlan"); value.Exists() && !data.Vlan.IsNull() { + data.Vlan = types.Int64Value(value.Int()) + } else { + data.Vlan = types.Int64Null() + } + if value := res.Get(prefix + "voice.vlan"); !data.VoiceVlan.IsNull() { + if value.Exists() { + data.VoiceVlan = types.BoolValue(true) + } else { + data.VoiceVlan = types.BoolValue(false) + } + } else { + data.VoiceVlan = types.BoolNull() + } + if value := res.Get(prefix + "linksec.policy"); value.Exists() && !data.LinksecPolicy.IsNull() { + data.LinksecPolicy = types.StringValue(value.String()) + } else { + data.LinksecPolicy = types.StringNull() + } + if value := res.Get(prefix + "sgt"); value.Exists() && !data.Sgt.IsNull() { + data.Sgt = types.Int64Value(value.Int()) + } else { + data.Sgt = types.Int64Null() + } + if value := res.Get(prefix + "absolute-timer"); value.Exists() && !data.AbsoluteTimer.IsNull() { + data.AbsoluteTimer = types.Int64Value(value.Int()) + } else { + data.AbsoluteTimer = types.Int64Null() + } + if value := res.Get(prefix + "description"); value.Exists() && !data.Description.IsNull() { + data.Description = types.StringValue(value.String()) + } else { + data.Description = types.StringNull() + } + for i := range data.InterfaceTemplate { + keys := [...]string{"name"} + keyValues := [...]string{data.InterfaceTemplate[i].Name.ValueString()} + + var r gjson.Result + res.Get(prefix + "interface-template").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("name"); value.Exists() && !data.InterfaceTemplate[i].Name.IsNull() { + data.InterfaceTemplate[i].Name = types.StringValue(value.String()) + } else { + data.InterfaceTemplate[i].Name = types.StringNull() + } + } + if value := res.Get(prefix + "tunnel.type.capwap.name"); value.Exists() && !data.TunnelCapwapName.IsNull() { + data.TunnelCapwapName = types.StringValue(value.String()) + } else { + data.TunnelCapwapName = types.StringNull() + } + if value := res.Get(prefix + "vnid"); value.Exists() && !data.Vnid.IsNull() { + data.Vnid = types.StringValue(value.String()) + } else { + data.Vnid = types.StringNull() + } + if value := res.Get(prefix + "redirect.append.client-mac"); value.Exists() && !data.RedirectAppendClientMac.IsNull() { + data.RedirectAppendClientMac = types.StringValue(value.String()) + } else { + data.RedirectAppendClientMac = types.StringNull() + } + if value := res.Get(prefix + "redirect.append.switch-mac"); value.Exists() && !data.RedirectAppendSwitchMac.IsNull() { + data.RedirectAppendSwitchMac = types.StringValue(value.String()) + } else { + data.RedirectAppendSwitchMac = types.StringNull() + } + if value := res.Get(prefix + "redirect.url.url_name"); value.Exists() && !data.RedirectUrlUrlName.IsNull() { + data.RedirectUrlUrlName = types.StringValue(value.String()) + } else { + data.RedirectUrlUrlName = types.StringNull() + } + if value := res.Get(prefix + "redirect.url.match.acl_name"); value.Exists() && !data.RedirectUrlMatchAclName.IsNull() { + data.RedirectUrlMatchAclName = types.StringValue(value.String()) + } else { + data.RedirectUrlMatchAclName = types.StringNull() + } + if value := res.Get(prefix + "redirect.url.match.action"); value.Exists() && !data.RedirectUrlMatchAction.IsNull() { + data.RedirectUrlMatchAction = types.StringValue(value.String()) + } else { + data.RedirectUrlMatchAction = types.StringNull() + } + if value := res.Get(prefix + "dns-acl.preauth"); value.Exists() && !data.DnsAclPreauth.IsNull() { + data.DnsAclPreauth = types.StringValue(value.String()) + } else { + data.DnsAclPreauth = types.StringNull() + } + if value := res.Get(prefix + "service-policy.qos.input"); value.Exists() && !data.ServicePolicyQosInput.IsNull() { + data.ServicePolicyQosInput = types.StringValue(value.String()) + } else { + data.ServicePolicyQosInput = types.StringNull() + } + if value := res.Get(prefix + "service-policy.qos.output"); value.Exists() && !data.ServicePolicyQosOutput.IsNull() { + data.ServicePolicyQosOutput = types.StringValue(value.String()) + } else { + data.ServicePolicyQosOutput = types.StringNull() + } + for i := range data.TagConfig { + keys := [...]string{"name"} + keyValues := [...]string{data.TagConfig[i].Name.ValueString()} + + var r gjson.Result + res.Get(prefix + "tag-config").ForEach( + func(_, v gjson.Result) bool { + found := false + for ik := range keys { + if v.Get(keys[ik]).String() == keyValues[ik] { + found = true + continue + } + found = false + break + } + if found { + r = v + return false + } + return true + }, + ) + if value := r.Get("name"); value.Exists() && !data.TagConfig[i].Name.IsNull() { + data.TagConfig[i].Name = types.StringValue(value.String()) + } else { + data.TagConfig[i].Name = types.StringNull() + } + } + if value := res.Get(prefix + "mdns-service-policy"); value.Exists() && !data.MdnsServicePolicy.IsNull() { + data.MdnsServicePolicy = types.StringValue(value.String()) + } else { + data.MdnsServicePolicy = types.StringNull() + } +} + +func (data *ServiceTemplateData) fromBody(ctx context.Context, res gjson.Result) { + prefix := helpers.LastElement(data.getPath()) + "." + if res.Get(helpers.LastElement(data.getPath())).IsArray() { + prefix += "0." + } + if value := res.Get(prefix + "access-group-config"); value.Exists() { + data.AccessGroup = make([]ServiceTemplateAccessGroup, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := ServiceTemplateAccessGroup{} + if cValue := v.Get("name"); cValue.Exists() { + item.Name = types.StringValue(cValue.String()) + } + data.AccessGroup = append(data.AccessGroup, item) + return true + }) + } + if value := res.Get(prefix + "inactivity-timer.value"); value.Exists() { + data.InactivityTimerValue = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "inactivity-timer.probe"); value.Exists() { + data.InactivityTimerProbe = types.BoolValue(true) + } else { + data.InactivityTimerProbe = types.BoolValue(false) + } + if value := res.Get(prefix + "vlan"); value.Exists() { + data.Vlan = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "voice.vlan"); value.Exists() { + data.VoiceVlan = types.BoolValue(true) + } else { + data.VoiceVlan = types.BoolValue(false) + } + if value := res.Get(prefix + "linksec.policy"); value.Exists() { + data.LinksecPolicy = types.StringValue(value.String()) + } + if value := res.Get(prefix + "sgt"); value.Exists() { + data.Sgt = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "absolute-timer"); value.Exists() { + data.AbsoluteTimer = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "description"); value.Exists() { + data.Description = types.StringValue(value.String()) + } + if value := res.Get(prefix + "interface-template"); value.Exists() { + data.InterfaceTemplate = make([]ServiceTemplateInterfaceTemplate, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := ServiceTemplateInterfaceTemplate{} + if cValue := v.Get("name"); cValue.Exists() { + item.Name = types.StringValue(cValue.String()) + } + data.InterfaceTemplate = append(data.InterfaceTemplate, item) + return true + }) + } + if value := res.Get(prefix + "tunnel.type.capwap.name"); value.Exists() { + data.TunnelCapwapName = types.StringValue(value.String()) + } + if value := res.Get(prefix + "vnid"); value.Exists() { + data.Vnid = types.StringValue(value.String()) + } + if value := res.Get(prefix + "redirect.append.client-mac"); value.Exists() { + data.RedirectAppendClientMac = types.StringValue(value.String()) + } + if value := res.Get(prefix + "redirect.append.switch-mac"); value.Exists() { + data.RedirectAppendSwitchMac = types.StringValue(value.String()) + } + if value := res.Get(prefix + "redirect.url.url_name"); value.Exists() { + data.RedirectUrlUrlName = types.StringValue(value.String()) + } + if value := res.Get(prefix + "redirect.url.match.acl_name"); value.Exists() { + data.RedirectUrlMatchAclName = types.StringValue(value.String()) + } + if value := res.Get(prefix + "redirect.url.match.action"); value.Exists() { + data.RedirectUrlMatchAction = types.StringValue(value.String()) + } + if value := res.Get(prefix + "dns-acl.preauth"); value.Exists() { + data.DnsAclPreauth = types.StringValue(value.String()) + } + if value := res.Get(prefix + "service-policy.qos.input"); value.Exists() { + data.ServicePolicyQosInput = types.StringValue(value.String()) + } + if value := res.Get(prefix + "service-policy.qos.output"); value.Exists() { + data.ServicePolicyQosOutput = types.StringValue(value.String()) + } + if value := res.Get(prefix + "tag-config"); value.Exists() { + data.TagConfig = make([]ServiceTemplateTagConfig, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := ServiceTemplateTagConfig{} + if cValue := v.Get("name"); cValue.Exists() { + item.Name = types.StringValue(cValue.String()) + } + data.TagConfig = append(data.TagConfig, item) + return true + }) + } + if value := res.Get(prefix + "mdns-service-policy"); value.Exists() { + data.MdnsServicePolicy = types.StringValue(value.String()) + } +} + +func (data *ServiceTemplate) getDeletedListItems(ctx context.Context, state ServiceTemplate) []string { + deletedListItems := make([]string, 0) + for i := range state.AccessGroup { + stateKeyValues := [...]string{state.AccessGroup[i].Name.ValueString()} + + emptyKeys := true + if !reflect.ValueOf(state.AccessGroup[i].Name.ValueString()).IsZero() { + emptyKeys = false + } + if emptyKeys { + continue + } + + found := false + for j := range data.AccessGroup { + found = true + if state.AccessGroup[i].Name.ValueString() != data.AccessGroup[j].Name.ValueString() { + found = false + } + if found { + break + } + } + if !found { + deletedListItems = append(deletedListItems, fmt.Sprintf("%v/access-group-config=%v", state.getPath(), strings.Join(stateKeyValues[:], ","))) + } + } + for i := range state.InterfaceTemplate { + stateKeyValues := [...]string{state.InterfaceTemplate[i].Name.ValueString()} + + emptyKeys := true + if !reflect.ValueOf(state.InterfaceTemplate[i].Name.ValueString()).IsZero() { + emptyKeys = false + } + if emptyKeys { + continue + } + + found := false + for j := range data.InterfaceTemplate { + found = true + if state.InterfaceTemplate[i].Name.ValueString() != data.InterfaceTemplate[j].Name.ValueString() { + found = false + } + if found { + break + } + } + if !found { + deletedListItems = append(deletedListItems, fmt.Sprintf("%v/interface-template=%v", state.getPath(), strings.Join(stateKeyValues[:], ","))) + } + } + for i := range state.TagConfig { + stateKeyValues := [...]string{state.TagConfig[i].Name.ValueString()} + + emptyKeys := true + if !reflect.ValueOf(state.TagConfig[i].Name.ValueString()).IsZero() { + emptyKeys = false + } + if emptyKeys { + continue + } + + found := false + for j := range data.TagConfig { + found = true + if state.TagConfig[i].Name.ValueString() != data.TagConfig[j].Name.ValueString() { + found = false + } + if found { + break + } + } + if !found { + deletedListItems = append(deletedListItems, fmt.Sprintf("%v/tag-config=%v", state.getPath(), strings.Join(stateKeyValues[:], ","))) + } + } + return deletedListItems +} + +func (data *ServiceTemplate) getEmptyLeafsDelete(ctx context.Context) []string { + emptyLeafsDelete := make([]string, 0) + + if !data.InactivityTimerProbe.IsNull() && !data.InactivityTimerProbe.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/inactivity-timer/probe", data.getPath())) + } + if !data.VoiceVlan.IsNull() && !data.VoiceVlan.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/voice/vlan", data.getPath())) + } + + return emptyLeafsDelete +} + +func (data *ServiceTemplate) getDeletePaths(ctx context.Context) []string { + var deletePaths []string + for i := range data.AccessGroup { + keyValues := [...]string{data.AccessGroup[i].Name.ValueString()} + + deletePaths = append(deletePaths, fmt.Sprintf("%v/access-group-config=%v", data.getPath(), strings.Join(keyValues[:], ","))) + } + if !data.InactivityTimerValue.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/inactivity-timer/value", data.getPath())) + } + if !data.InactivityTimerProbe.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/inactivity-timer/probe", data.getPath())) + } + if !data.Vlan.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/vlan", data.getPath())) + } + if !data.VoiceVlan.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/voice/vlan", data.getPath())) + } + if !data.LinksecPolicy.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/linksec/policy", data.getPath())) + } + if !data.Sgt.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/sgt", data.getPath())) + } + if !data.AbsoluteTimer.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/absolute-timer", data.getPath())) + } + if !data.Description.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/description", data.getPath())) + } + for i := range data.InterfaceTemplate { + keyValues := [...]string{data.InterfaceTemplate[i].Name.ValueString()} + + deletePaths = append(deletePaths, fmt.Sprintf("%v/interface-template=%v", data.getPath(), strings.Join(keyValues[:], ","))) + } + if !data.TunnelCapwapName.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/tunnel/type/capwap/name", data.getPath())) + } + if !data.Vnid.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/vnid", data.getPath())) + } + if !data.RedirectAppendClientMac.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/redirect/append/client-mac", data.getPath())) + } + if !data.RedirectAppendSwitchMac.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/redirect/append/switch-mac", data.getPath())) + } + if !data.RedirectUrlUrlName.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/redirect/url/url_name", data.getPath())) + } + if !data.RedirectUrlMatchAclName.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/redirect/url/match/acl_name", data.getPath())) + } + if !data.RedirectUrlMatchAction.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/redirect/url/match/action", data.getPath())) + } + if !data.DnsAclPreauth.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/dns-acl/preauth", data.getPath())) + } + if !data.ServicePolicyQosInput.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/service-policy/qos/input", data.getPath())) + } + if !data.ServicePolicyQosOutput.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/service-policy/qos/output", data.getPath())) + } + for i := range data.TagConfig { + keyValues := [...]string{data.TagConfig[i].Name.ValueString()} + + deletePaths = append(deletePaths, fmt.Sprintf("%v/tag-config=%v", data.getPath(), strings.Join(keyValues[:], ","))) + } + if !data.MdnsServicePolicy.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/mdns-service-policy", data.getPath())) + } + return deletePaths +} diff --git a/internal/provider/model_iosxe_template.go b/internal/provider/model_iosxe_template.go index 022399bb..5c0806aa 100644 --- a/internal/provider/model_iosxe_template.go +++ b/internal/provider/model_iosxe_template.go @@ -42,6 +42,8 @@ type Template struct { Dot1xPae types.String `tfsdk:"dot1x_pae"` Dot1xMaxReauthReq types.Int64 `tfsdk:"dot1x_max_reauth_req"` Dot1xMaxReq types.Int64 `tfsdk:"dot1x_max_req"` + Dot1xTimeoutTxPeriod types.Int64 `tfsdk:"dot1x_timeout_tx_period"` + ServicePolicySubscriber types.String `tfsdk:"service_policy_subscriber"` ServicePolicyInput types.String `tfsdk:"service_policy_input"` ServicePolicyOutput types.String `tfsdk:"service_policy_output"` SourceTemplate types.String `tfsdk:"source_template"` @@ -117,6 +119,8 @@ type TemplateData struct { Dot1xPae types.String `tfsdk:"dot1x_pae"` Dot1xMaxReauthReq types.Int64 `tfsdk:"dot1x_max_reauth_req"` Dot1xMaxReq types.Int64 `tfsdk:"dot1x_max_req"` + Dot1xTimeoutTxPeriod types.Int64 `tfsdk:"dot1x_timeout_tx_period"` + ServicePolicySubscriber types.String `tfsdk:"service_policy_subscriber"` ServicePolicyInput types.String `tfsdk:"service_policy_input"` ServicePolicyOutput types.String `tfsdk:"service_policy_output"` SourceTemplate types.String `tfsdk:"source_template"` @@ -231,6 +235,12 @@ func (data Template) toBody(ctx context.Context) string { if !data.Dot1xMaxReq.IsNull() && !data.Dot1xMaxReq.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"dot1x.max-req", strconv.FormatInt(data.Dot1xMaxReq.ValueInt64(), 10)) } + if !data.Dot1xTimeoutTxPeriod.IsNull() && !data.Dot1xTimeoutTxPeriod.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"dot1x.timeout.tx-period", strconv.FormatInt(data.Dot1xTimeoutTxPeriod.ValueInt64(), 10)) + } + if !data.ServicePolicySubscriber.IsNull() && !data.ServicePolicySubscriber.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"service-policy.type.control.subscriber", data.ServicePolicySubscriber.ValueString()) + } if !data.ServicePolicyInput.IsNull() && !data.ServicePolicyInput.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"service-policy.input.policy-map-name", data.ServicePolicyInput.ValueString()) } @@ -554,6 +564,16 @@ func (data *Template) updateFromBody(ctx context.Context, res gjson.Result) { } else { data.Dot1xMaxReq = types.Int64Null() } + if value := res.Get(prefix + "dot1x.timeout.tx-period"); value.Exists() && !data.Dot1xTimeoutTxPeriod.IsNull() { + data.Dot1xTimeoutTxPeriod = types.Int64Value(value.Int()) + } else { + data.Dot1xTimeoutTxPeriod = types.Int64Null() + } + if value := res.Get(prefix + "service-policy.type.control.subscriber"); value.Exists() && !data.ServicePolicySubscriber.IsNull() { + data.ServicePolicySubscriber = types.StringValue(value.String()) + } else { + data.ServicePolicySubscriber = types.StringNull() + } if value := res.Get(prefix + "service-policy.input.policy-map-name"); value.Exists() && !data.ServicePolicyInput.IsNull() { data.ServicePolicyInput = types.StringValue(value.String()) } else { @@ -1140,6 +1160,12 @@ func (data *TemplateData) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get(prefix + "dot1x.max-req"); value.Exists() { data.Dot1xMaxReq = types.Int64Value(value.Int()) } + if value := res.Get(prefix + "dot1x.timeout.tx-period"); value.Exists() { + data.Dot1xTimeoutTxPeriod = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "service-policy.type.control.subscriber"); value.Exists() { + data.ServicePolicySubscriber = types.StringValue(value.String()) + } if value := res.Get(prefix + "service-policy.input.policy-map-name"); value.Exists() { data.ServicePolicyInput = types.StringValue(value.String()) } @@ -1868,6 +1894,12 @@ func (data *Template) getDeletePaths(ctx context.Context) []string { if !data.Dot1xMaxReq.IsNull() { deletePaths = append(deletePaths, fmt.Sprintf("%v/dot1x/max-req", data.getPath())) } + if !data.Dot1xTimeoutTxPeriod.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/dot1x/timeout/tx-period", data.getPath())) + } + if !data.ServicePolicySubscriber.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/service-policy/type/control/subscriber", data.getPath())) + } if !data.ServicePolicyInput.IsNull() { deletePaths = append(deletePaths, fmt.Sprintf("%v/service-policy/input/policy-map-name", data.getPath())) } diff --git a/internal/provider/model_iosxe_vlan_access_map.go b/internal/provider/model_iosxe_vlan_access_map.go new file mode 100644 index 00000000..4653968d --- /dev/null +++ b/internal/provider/model_iosxe_vlan_access_map.go @@ -0,0 +1,172 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + "net/url" + "regexp" + "strconv" + + "github.com/CiscoDevNet/terraform-provider-iosxe/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tidwall/gjson" + "github.com/tidwall/sjson" +) + +type VLANAccessMap struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Value types.Int64 `tfsdk:"value"` + MatchIpv6Address types.List `tfsdk:"match_ipv6_address"` + MatchIpAddress types.List `tfsdk:"match_ip_address"` + Action types.String `tfsdk:"action"` +} + +type VLANAccessMapData struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Value types.Int64 `tfsdk:"value"` + MatchIpv6Address types.List `tfsdk:"match_ipv6_address"` + MatchIpAddress types.List `tfsdk:"match_ip_address"` + Action types.String `tfsdk:"action"` +} + +func (data VLANAccessMap) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=%v,%s", url.QueryEscape(fmt.Sprintf("%v", data.Name.ValueString())), url.QueryEscape(fmt.Sprintf("%v", data.Value.ValueInt64()))) +} + +func (data VLANAccessMapData) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=%v,%s", url.QueryEscape(fmt.Sprintf("%v", data.Name.ValueString())), url.QueryEscape(fmt.Sprintf("%v", data.Value.ValueInt64()))) +} + +// if last path element has a key -> remove it +func (data VLANAccessMap) getPathShort() string { + path := data.getPath() + re := regexp.MustCompile(`(.*)=[^\/]*$`) + matches := re.FindStringSubmatch(path) + if len(matches) <= 1 { + return path + } + return matches[1] +} + +func (data VLANAccessMap) toBody(ctx context.Context) string { + body := `{"` + helpers.LastElement(data.getPath()) + `":{}}` + if !data.Name.IsNull() && !data.Name.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"name", data.Name.ValueString()) + } + if !data.Value.IsNull() && !data.Value.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"value", strconv.FormatInt(data.Value.ValueInt64(), 10)) + } + if !data.MatchIpv6Address.IsNull() && !data.MatchIpv6Address.IsUnknown() { + var values []string + data.MatchIpv6Address.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"match.ipv6.address", values) + } + if !data.MatchIpAddress.IsNull() && !data.MatchIpAddress.IsUnknown() { + var values []string + data.MatchIpAddress.ElementsAs(ctx, &values, false) + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"match.ip.address", values) + } + if !data.Action.IsNull() && !data.Action.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"action", data.Action.ValueString()) + } + return body +} + +func (data *VLANAccessMap) updateFromBody(ctx context.Context, res gjson.Result) { + prefix := helpers.LastElement(data.getPath()) + "." + if res.Get(helpers.LastElement(data.getPath())).IsArray() { + prefix += "0." + } + if value := res.Get(prefix + "name"); value.Exists() && !data.Name.IsNull() { + data.Name = types.StringValue(value.String()) + } else { + data.Name = types.StringNull() + } + if value := res.Get(prefix + "value"); value.Exists() && !data.Value.IsNull() { + data.Value = types.Int64Value(value.Int()) + } else { + data.Value = types.Int64Null() + } + if value := res.Get(prefix + "match.ipv6.address"); value.Exists() && !data.MatchIpv6Address.IsNull() { + data.MatchIpv6Address = helpers.GetStringList(value.Array()) + } else { + data.MatchIpv6Address = types.ListNull(types.StringType) + } + if value := res.Get(prefix + "match.ip.address"); value.Exists() && !data.MatchIpAddress.IsNull() { + data.MatchIpAddress = helpers.GetStringList(value.Array()) + } else { + data.MatchIpAddress = types.ListNull(types.StringType) + } + if value := res.Get(prefix + "action"); value.Exists() && !data.Action.IsNull() { + data.Action = types.StringValue(value.String()) + } else { + data.Action = types.StringNull() + } +} + +func (data *VLANAccessMapData) fromBody(ctx context.Context, res gjson.Result) { + prefix := helpers.LastElement(data.getPath()) + "." + if res.Get(helpers.LastElement(data.getPath())).IsArray() { + prefix += "0." + } + if value := res.Get(prefix + "match.ipv6.address"); value.Exists() { + data.MatchIpv6Address = helpers.GetStringList(value.Array()) + } else { + data.MatchIpv6Address = types.ListNull(types.StringType) + } + if value := res.Get(prefix + "match.ip.address"); value.Exists() { + data.MatchIpAddress = helpers.GetStringList(value.Array()) + } else { + data.MatchIpAddress = types.ListNull(types.StringType) + } + if value := res.Get(prefix + "action"); value.Exists() { + data.Action = types.StringValue(value.String()) + } +} + +func (data *VLANAccessMap) getDeletedListItems(ctx context.Context, state VLANAccessMap) []string { + deletedListItems := make([]string, 0) + return deletedListItems +} + +func (data *VLANAccessMap) getEmptyLeafsDelete(ctx context.Context) []string { + emptyLeafsDelete := make([]string, 0) + return emptyLeafsDelete +} + +func (data *VLANAccessMap) getDeletePaths(ctx context.Context) []string { + var deletePaths []string + if !data.MatchIpv6Address.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/match/ipv6/address", data.getPath())) + } + if !data.MatchIpAddress.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/match/ip/address", data.getPath())) + } + if !data.Action.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/action", data.getPath())) + } + return deletePaths +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 66c556b4..0288b130 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -346,6 +346,7 @@ func (p *IosxeProvider) Resources(ctx context.Context) []func() resource.Resourc NewRadiusServerResource, NewRouteMapResource, NewServiceResource, + NewServiceTemplateResource, NewSNMPServerResource, NewSNMPServerGroupResource, NewSNMPServerUserResource, @@ -357,6 +358,7 @@ func (p *IosxeProvider) Resources(ctx context.Context) []func() resource.Resourc NewUDLDResource, NewUsernameResource, NewVLANResource, + NewVLANAccessMapResource, NewVLANConfigurationResource, NewVRFResource, NewVTPResource, @@ -437,6 +439,7 @@ func (p *IosxeProvider) DataSources(ctx context.Context) []func() datasource.Dat NewRadiusServerDataSource, NewRouteMapDataSource, NewServiceDataSource, + NewServiceTemplateDataSource, NewSNMPServerDataSource, NewSNMPServerGroupDataSource, NewSNMPServerUserDataSource, @@ -448,6 +451,7 @@ func (p *IosxeProvider) DataSources(ctx context.Context) []func() datasource.Dat NewUDLDDataSource, NewUsernameDataSource, NewVLANDataSource, + NewVLANAccessMapDataSource, NewVLANConfigurationDataSource, NewVRFDataSource, NewVTPDataSource, diff --git a/internal/provider/resource_iosxe_aaa.go b/internal/provider/resource_iosxe_aaa.go index 8e9afc7c..a1869b2f 100644 --- a/internal/provider/resource_iosxe_aaa.go +++ b/internal/provider/resource_iosxe_aaa.go @@ -25,6 +25,7 @@ import ( "regexp" "github.com/CiscoDevNet/terraform-provider-iosxe/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -131,6 +132,13 @@ func (r *AAAResource) Schema(ctx context.Context, req resource.SchemaRequest, re }, }, }, + "ip_radius_source_interface_loopback": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Loopback interface").AddIntegerRangeDescription(0, 2147483647).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(0, 2147483647), + }, + }, }, }, }, diff --git a/internal/provider/resource_iosxe_aaa_authorization.go b/internal/provider/resource_iosxe_aaa_authorization.go index 4e1f9a0a..b11e8094 100644 --- a/internal/provider/resource_iosxe_aaa_authorization.go +++ b/internal/provider/resource_iosxe_aaa_authorization.go @@ -85,6 +85,14 @@ func (r *AAAAuthorizationResource) Schema(ctx context.Context, req resource.Sche MarkdownDescription: helpers.NewAttributeDescription("Use local database").String, Optional: true, }, + "a1_group": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Use Server-group").String, + Optional: true, + }, + "a2_local": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, "a1_if_authenticated": schema.BoolAttribute{ MarkdownDescription: helpers.NewAttributeDescription("Succeed if user has authenticated.").String, Optional: true, diff --git a/internal/provider/resource_iosxe_aaa_authorization_test.go b/internal/provider/resource_iosxe_aaa_authorization_test.go index 4da5086f..5ca2c2bc 100644 --- a/internal/provider/resource_iosxe_aaa_authorization_test.go +++ b/internal/provider/resource_iosxe_aaa_authorization_test.go @@ -33,6 +33,7 @@ func TestAccIosxeAAAAuthorization(t *testing.T) { var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa_authorization.test", "execs.0.name", "TEST")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa_authorization.test", "execs.0.a1_local", "false")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa_authorization.test", "execs.0.a1_group", "false")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa_authorization.test", "execs.0.a1_if_authenticated", "true")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -65,6 +66,7 @@ func testAccIosxeAAAAuthorizationConfig_all() string { config += ` execs = [{` + "\n" config += ` name = "TEST"` + "\n" config += ` a1_local = false` + "\n" + config += ` a1_group = "false"` + "\n" config += ` a1_if_authenticated = true` + "\n" config += ` }]` + "\n" config += `}` + "\n" diff --git a/internal/provider/resource_iosxe_aaa_test.go b/internal/provider/resource_iosxe_aaa_test.go index 9473f5e9..92c01170 100644 --- a/internal/provider/resource_iosxe_aaa_test.go +++ b/internal/provider/resource_iosxe_aaa_test.go @@ -39,6 +39,7 @@ func TestAccIosxeAAA(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "server_radius_dynamic_author_clients.0.server_key", "abcd123")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "group_server_radius.0.name", "T-Group")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "group_server_radius.0.server_names.0.name", "TESTRADIUS")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "group_server_radius.0.ip_radius_source_interface_loopback", "0")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "group_tacacsplus.0.name", "tacacs-group")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_aaa.test", "group_tacacsplus.0.servers.0.name", "tacacs_10.10.15.12")) resource.Test(t, resource.TestCase{ @@ -82,6 +83,7 @@ func testAccIosxeAAAConfig_all() string { config += ` server_names = [{` + "\n" config += ` name = "TESTRADIUS"` + "\n" config += ` }]` + "\n" + config += ` ip_radius_source_interface_loopback = 0` + "\n" config += ` }]` + "\n" config += ` group_tacacsplus = [{` + "\n" config += ` name = "tacacs-group"` + "\n" diff --git a/internal/provider/resource_iosxe_interface_tunnel.go b/internal/provider/resource_iosxe_interface_tunnel.go index 3a4f8b2b..ee25e6ec 100644 --- a/internal/provider/resource_iosxe_interface_tunnel.go +++ b/internal/provider/resource_iosxe_interface_tunnel.go @@ -198,6 +198,13 @@ func (r *InterfaceTunnelResource) Schema(ctx context.Context, req resource.Schem stringvalidator.OneOf("clear", "copy", "set"), }, }, + "arp_timeout": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set ARP cache timeout").AddIntegerRangeDescription(0, 2147483).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(0, 2147483), + }, + }, "ipv4_address": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("").String, Optional: true, diff --git a/internal/provider/resource_iosxe_interface_tunnel_test.go b/internal/provider/resource_iosxe_interface_tunnel_test.go index 691ff88d..1aebd17d 100644 --- a/internal/provider/resource_iosxe_interface_tunnel_test.go +++ b/internal/provider/resource_iosxe_interface_tunnel_test.go @@ -45,6 +45,7 @@ func TestAccIosxeInterfaceTunnel(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "ipv6_addresses.0.eui_64", "true")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "tunnel_destination_ipv4", "2.2.2.2")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "crypto_ipsec_df_bit", "clear")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "arp_timeout", "300")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "ipv4_address", "10.1.1.1")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "ipv4_address_mask", "255.255.255.0")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_interface_tunnel.test", "ip_dhcp_relay_source_interface", "Loopback100")) @@ -131,6 +132,7 @@ func testAccIosxeInterfaceTunnelConfig_all() string { config += ` }]` + "\n" config += ` tunnel_destination_ipv4 = "2.2.2.2"` + "\n" config += ` crypto_ipsec_df_bit = "clear"` + "\n" + config += ` arp_timeout = 300` + "\n" config += ` ipv4_address = "10.1.1.1"` + "\n" config += ` ipv4_address_mask = "255.255.255.0"` + "\n" config += ` ip_dhcp_relay_source_interface = "Loopback100"` + "\n" diff --git a/internal/provider/resource_iosxe_service_template.go b/internal/provider/resource_iosxe_service_template.go new file mode 100644 index 00000000..970ba14d --- /dev/null +++ b/internal/provider/resource_iosxe_service_template.go @@ -0,0 +1,450 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + + "github.com/CiscoDevNet/terraform-provider-iosxe/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-restconf" +) + +func NewServiceTemplateResource() resource.Resource { + return &ServiceTemplateResource{} +} + +type ServiceTemplateResource struct { + clients map[string]*restconf.Client +} + +func (r *ServiceTemplateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_service_template" +} + +func (r *ServiceTemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This resource can manage the Service Template configuration.", + + Attributes: map[string]schema.Attribute{ + "device": schema.StringAttribute{ + MarkdownDescription: "A device name from the provider configuration.", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The path of the object.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "word": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Specify a template name (maximum 48 characters)").String, + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 48), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "access_group": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Access list to be applied").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Specify the access list name").String, + Required: true, + }, + }, + }, + }, + "inactivity_timer_value": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enter a value between 1 and 65535").AddIntegerRangeDescription(1, 65535).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "inactivity_timer_probe": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("ARP probe").String, + Optional: true, + }, + "vlan": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Vlan to be applied").AddIntegerRangeDescription(1, 4094).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 4094), + }, + }, + "voice_vlan": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Critical voice vlan").String, + Optional: true, + }, + "linksec_policy": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set the link security policy").AddStringEnumDescription("must-not-secure", "must-secure", "should-secure").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("must-not-secure", "must-secure", "should-secure"), + }, + }, + "sgt": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("SGT tag").AddIntegerRangeDescription(2, 65519).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(2, 65519), + }, + }, + "absolute_timer": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enter a value between 1 and 1073741823").AddIntegerRangeDescription(1, 1073741823).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 1073741823), + }, + }, + "description": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enter a description").String, + Optional: true, + }, + "interface_template": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Interface template to be applied").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Enter name of interface template").String, + Required: true, + }, + }, + }, + }, + "tunnel_capwap_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("tunnel profile name").String, + Optional: true, + }, + "vnid": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Vnid to be applied").String, + Optional: true, + }, + "redirect_append_client_mac": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Append client Mac Address in redirect URL").String, + Optional: true, + }, + "redirect_append_switch_mac": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Append switch Mac Address in redirect URL").String, + Optional: true, + }, + "redirect_url_url_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Specify a valid URL").String, + Optional: true, + }, + "redirect_url_match_acl_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Specify the access list name").String, + Optional: true, + }, + "redirect_url_match_action": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").AddStringEnumDescription("one-time-redirect", "redirect-on-no-match").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("one-time-redirect", "redirect-on-no-match"), + }, + }, + "dns_acl_preauth": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("pre-authentication").String, + Optional: true, + }, + "service_policy_qos_input": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Configure input Qos policy").String, + Optional: true, + }, + "service_policy_qos_output": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Configure output Qos policy").String, + Optional: true, + }, + "tag_config": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("tag name").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Specify the Tag name").String, + Required: true, + }, + }, + }, + }, + "mdns_service_policy": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("mdns policy to be applied").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 164), + }, + }, + }, + } +} + +func (r *ServiceTemplateResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.clients = req.ProviderData.(map[string]*restconf.Client) +} + +func (r *ServiceTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ServiceTemplate + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[plan.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.getPath())) + + // Create object + body := plan.toBody(ctx) + + emptyLeafsDelete := plan.getEmptyLeafsDelete(ctx) + tflog.Debug(ctx, fmt.Sprintf("List of empty leafs to delete: %+v", emptyLeafsDelete)) + + if YangPatch { + edits := []restconf.YangPatchEdit{restconf.NewYangPatchEdit("merge", plan.getPath(), restconf.Body{Str: body})} + for _, i := range emptyLeafsDelete { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[plan.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object, got error: %s", err)) + return + } + } else { + res, err := r.clients[plan.Device.ValueString()].PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = r.clients[plan.Device.ValueString()].PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH), got error: %s", err)) + return + } + for _, i := range emptyLeafsDelete { + res, err := r.clients[plan.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + + plan.Id = types.StringValue(plan.getPath()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.getPath())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *ServiceTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ServiceTemplate + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[state.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.ValueString())) + + res, err := r.clients[state.Device.ValueString()].GetData(state.Id.ValueString()) + if res.StatusCode == 404 { + state = ServiceTemplate{Device: state.Device, Id: state.Id} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + state.updateFromBody(ctx, res.Res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *ServiceTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ServiceTemplate + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[plan.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + body := plan.toBody(ctx) + + deletedListItems := plan.getDeletedListItems(ctx, state) + tflog.Debug(ctx, fmt.Sprintf("List items to delete: %+v", deletedListItems)) + + emptyLeafsDelete := plan.getEmptyLeafsDelete(ctx) + tflog.Debug(ctx, fmt.Sprintf("List of empty leafs to delete: %+v", emptyLeafsDelete)) + + if YangPatch { + edits := []restconf.YangPatchEdit{restconf.NewYangPatchEdit("merge", plan.getPath(), restconf.Body{Str: body})} + for _, i := range deletedListItems { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + for _, i := range emptyLeafsDelete { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[plan.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update object, got error: %s", err)) + return + } + } else { + res, err := r.clients[plan.Device.ValueString()].PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = r.clients[plan.Device.ValueString()].PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH), got error: %s", err)) + return + } + for _, i := range deletedListItems { + res, err := r.clients[state.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + for _, i := range emptyLeafsDelete { + res, err := r.clients[plan.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *ServiceTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ServiceTemplate + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[state.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + deleteMode := "all" + + if deleteMode == "all" { + res, err := r.clients[state.Device.ValueString()].DeleteData(state.Id.ValueString()) + if err != nil && res.StatusCode != 404 && res.StatusCode != 400 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } else { + deletePaths := state.getDeletePaths(ctx) + tflog.Debug(ctx, fmt.Sprintf("Paths to delete: %+v", deletePaths)) + + if YangPatch { + edits := []restconf.YangPatchEdit{} + for _, i := range deletePaths { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[state.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } else { + for _, i := range deletePaths { + res, err := r.clients[state.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +func (r *ServiceTemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/resource_iosxe_service_template_test.go b/internal/provider/resource_iosxe_service_template_test.go new file mode 100644 index 00000000..f7f66fb3 --- /dev/null +++ b/internal/provider/resource_iosxe_service_template_test.go @@ -0,0 +1,111 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccIosxeServiceTemplate(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "word", "DEFAULT_LINKSEC_POLICY_MUST_SECURE")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "access_group.0.name", "ag1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "inactivity_timer_value", "25")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "inactivity_timer_probe", "false")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "vlan", "27")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "voice_vlan", "false")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "linksec_policy", "must-secure")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "sgt", "57")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "absolute_timer", "45")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "description", "service_template_desc")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "interface_template.0.name", "template1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "tunnel_capwap_name", "tunnel_name")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "vnid", "vnid_1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "redirect_append_client_mac", "00:01:00:01:00:01")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "redirect_append_switch_mac", "00:01:00:01:00:02")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "redirect_url_url_name", "valid_url")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "redirect_url_match_acl_name", "acl_name")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "redirect_url_match_action", "redirect-on-no-match")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "dns_acl_preauth", "dns_acl_name")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "service_policy_qos_input", "input_qos")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "service_policy_qos_output", "output_qos")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_service_template.test", "tag_config.0.name", "tag_name")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccIosxeServiceTemplateConfig_minimum(), + }, + { + Config: testAccIosxeServiceTemplateConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + ResourceName: "iosxe_service_template.test", + ImportState: true, + ImportStateId: "Cisco-IOS-XE-native:native/Cisco-IOS-XE-switch:service-template=DEFAULT_LINKSEC_POLICY_MUST_SECURE", + }, + }, + }) +} + +func testAccIosxeServiceTemplateConfig_minimum() string { + config := `resource "iosxe_service_template" "test" {` + "\n" + config += ` word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE"` + "\n" + config += `}` + "\n" + return config +} + +func testAccIosxeServiceTemplateConfig_all() string { + config := `resource "iosxe_service_template" "test" {` + "\n" + config += ` word = "DEFAULT_LINKSEC_POLICY_MUST_SECURE"` + "\n" + config += ` access_group = [{` + "\n" + config += ` name = "ag1"` + "\n" + config += ` }]` + "\n" + config += ` inactivity_timer_value = 25` + "\n" + config += ` inactivity_timer_probe = false` + "\n" + config += ` vlan = 27` + "\n" + config += ` voice_vlan = false` + "\n" + config += ` linksec_policy = "must-secure"` + "\n" + config += ` sgt = 57` + "\n" + config += ` absolute_timer = 45` + "\n" + config += ` description = "service_template_desc"` + "\n" + config += ` interface_template = [{` + "\n" + config += ` name = "template1"` + "\n" + config += ` }]` + "\n" + config += ` tunnel_capwap_name = "tunnel_name"` + "\n" + config += ` vnid = "vnid_1"` + "\n" + config += ` redirect_append_client_mac = "00:01:00:01:00:01"` + "\n" + config += ` redirect_append_switch_mac = "00:01:00:01:00:02"` + "\n" + config += ` redirect_url_url_name = "valid_url"` + "\n" + config += ` redirect_url_match_acl_name = "acl_name"` + "\n" + config += ` redirect_url_match_action = "redirect-on-no-match"` + "\n" + config += ` dns_acl_preauth = "dns_acl_name"` + "\n" + config += ` service_policy_qos_input = "input_qos"` + "\n" + config += ` service_policy_qos_output = "output_qos"` + "\n" + config += ` tag_config = [{` + "\n" + config += ` name = "tag_name"` + "\n" + config += ` }]` + "\n" + config += `}` + "\n" + return config +} diff --git a/internal/provider/resource_iosxe_template.go b/internal/provider/resource_iosxe_template.go index 4ae613f8..822c89dc 100644 --- a/internal/provider/resource_iosxe_template.go +++ b/internal/provider/resource_iosxe_template.go @@ -102,6 +102,17 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques int64validator.Between(1, 10), }, }, + "dot1x_timeout_tx_period": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Timeout for supplicant retries").AddIntegerRangeDescription(1, 65535).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "service_policy_subscriber": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Apply a subscriber control policy to the interface").String, + Optional: true, + }, "service_policy_input": schema.StringAttribute{ MarkdownDescription: helpers.NewAttributeDescription("policy-map name").String, Optional: true, diff --git a/internal/provider/resource_iosxe_template_test.go b/internal/provider/resource_iosxe_template_test.go index f30eef85..a7f6170a 100644 --- a/internal/provider/resource_iosxe_template_test.go +++ b/internal/provider/resource_iosxe_template_test.go @@ -35,6 +35,8 @@ func TestAccIosxeTemplate(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "dot1x_pae", "both")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "dot1x_max_reauth_req", "3")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "dot1x_max_req", "3")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "dot1x_timeout_tx_period", "2")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "service_policy_subscriber", "dot1x_policy")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "service_policy_input", "SP1")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "service_policy_output", "SP2")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "switchport_mode_trunk", "true")) @@ -91,19 +93,15 @@ func TestAccIosxeTemplate(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "device_tracking", "true")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "device_tracking_vlan_range", "100-199")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "cts_manual", "true")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "cts_manual_policy_static_sgt", "100")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "cts_manual_policy_static_trusted", "false")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "cts_manual_propagate_sgt", "false")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_template.test", "cts_role_based_enforcement", "false")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccIosxeTemplateConfig_minimum(), + Config: testAccIosxeTemplatePrerequisitesConfig + testAccIosxeTemplateConfig_minimum(), }, { - Config: testAccIosxeTemplateConfig_all(), + Config: testAccIosxeTemplatePrerequisitesConfig + testAccIosxeTemplateConfig_all(), Check: resource.ComposeTestCheckFunc(checks...), }, { @@ -115,9 +113,22 @@ func TestAccIosxeTemplate(t *testing.T) { }) } +const testAccIosxeTemplatePrerequisitesConfig = ` +resource "iosxe_restconf" "PreReq0" { + path = "Cisco-IOS-XE-native:native/policy/Cisco-IOS-XE-policy:policy-map=dot1x_policy" + attributes = { + "name" = "dot1x_policy" + "type" = "control" + "subscriber" = "" + } +} + +` + func testAccIosxeTemplateConfig_minimum() string { config := `resource "iosxe_template" "test" {` + "\n" config += ` template_name = "TEMP1"` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, ]` + "\n" config += `}` + "\n" return config } @@ -128,6 +139,8 @@ func testAccIosxeTemplateConfig_all() string { config += ` dot1x_pae = "both"` + "\n" config += ` dot1x_max_reauth_req = 3` + "\n" config += ` dot1x_max_req = 3` + "\n" + config += ` dot1x_timeout_tx_period = 2` + "\n" + config += ` service_policy_subscriber = "dot1x_policy"` + "\n" config += ` service_policy_input = "SP1"` + "\n" config += ` service_policy_output = "SP2"` + "\n" config += ` switchport_mode_trunk = true` + "\n" @@ -188,10 +201,7 @@ func testAccIosxeTemplateConfig_all() string { config += ` device_tracking = true` + "\n" config += ` device_tracking_vlan_range = "100-199"` + "\n" config += ` cts_manual = true` + "\n" - config += ` cts_manual_policy_static_sgt = 100` + "\n" - config += ` cts_manual_policy_static_trusted = false` + "\n" - config += ` cts_manual_propagate_sgt = false` + "\n" - config += ` cts_role_based_enforcement = false` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, ]` + "\n" config += `}` + "\n" return config } diff --git a/internal/provider/resource_iosxe_vlan_access_map.go b/internal/provider/resource_iosxe_vlan_access_map.go new file mode 100644 index 00000000..6def215f --- /dev/null +++ b/internal/provider/resource_iosxe_vlan_access_map.go @@ -0,0 +1,342 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + + "github.com/CiscoDevNet/terraform-provider-iosxe/internal/provider/helpers" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/netascode/go-restconf" +) + +func NewVLANAccessMapResource() resource.Resource { + return &VLANAccessMapResource{} +} + +type VLANAccessMapResource struct { + clients map[string]*restconf.Client +} + +func (r *VLANAccessMapResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_vlan_access_map" +} + +func (r *VLANAccessMapResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "This resource can manage the VLAN Access Map configuration.", + + Attributes: map[string]schema.Attribute{ + "device": schema.StringAttribute{ + MarkdownDescription: "A device name from the provider configuration.", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "The path of the object.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Vlan access map tag").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "value": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Sequence to insert to/delete from existing vlan access-map entry").AddIntegerRangeDescription(0, 65535).String, + Required: true, + Validators: []validator.Int64{ + int64validator.Between(0, 65535), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + "match_ipv6_address": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Match IPv6 address to access control.").String, + ElementType: types.StringType, + Optional: true, + }, + "match_ip_address": schema.ListAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + ElementType: types.StringType, + Optional: true, + }, + "action": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Take the action").AddStringEnumDescription("drop", "forward").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("drop", "forward"), + }, + }, + }, + } +} + +func (r *VLANAccessMapResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.clients = req.ProviderData.(map[string]*restconf.Client) +} + +func (r *VLANAccessMapResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan VLANAccessMap + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[plan.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.getPath())) + + // Create object + body := plan.toBody(ctx) + + emptyLeafsDelete := plan.getEmptyLeafsDelete(ctx) + tflog.Debug(ctx, fmt.Sprintf("List of empty leafs to delete: %+v", emptyLeafsDelete)) + + if YangPatch { + edits := []restconf.YangPatchEdit{restconf.NewYangPatchEdit("merge", plan.getPath(), restconf.Body{Str: body})} + for _, i := range emptyLeafsDelete { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[plan.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object, got error: %s", err)) + return + } + } else { + res, err := r.clients[plan.Device.ValueString()].PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = r.clients[plan.Device.ValueString()].PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH), got error: %s", err)) + return + } + for _, i := range emptyLeafsDelete { + res, err := r.clients[plan.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + + plan.Id = types.StringValue(plan.getPath()) + + tflog.Debug(ctx, fmt.Sprintf("%s: Create finished successfully", plan.getPath())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VLANAccessMapResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state VLANAccessMap + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[state.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.ValueString())) + + res, err := r.clients[state.Device.ValueString()].GetData(state.Id.ValueString()) + if res.StatusCode == 404 { + state = VLANAccessMap{Device: state.Device, Id: state.Id} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object, got error: %s", err)) + return + } + + state.updateFromBody(ctx, res.Res) + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Read finished successfully", state.Id.ValueString())) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *VLANAccessMapResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state VLANAccessMap + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Read state + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[plan.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + body := plan.toBody(ctx) + + deletedListItems := plan.getDeletedListItems(ctx, state) + tflog.Debug(ctx, fmt.Sprintf("List items to delete: %+v", deletedListItems)) + + emptyLeafsDelete := plan.getEmptyLeafsDelete(ctx) + tflog.Debug(ctx, fmt.Sprintf("List of empty leafs to delete: %+v", emptyLeafsDelete)) + + if YangPatch { + edits := []restconf.YangPatchEdit{restconf.NewYangPatchEdit("merge", plan.getPath(), restconf.Body{Str: body})} + for _, i := range deletedListItems { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + for _, i := range emptyLeafsDelete { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[plan.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update object, got error: %s", err)) + return + } + } else { + res, err := r.clients[plan.Device.ValueString()].PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = r.clients[plan.Device.ValueString()].PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH), got error: %s", err)) + return + } + for _, i := range deletedListItems { + res, err := r.clients[state.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + for _, i := range emptyLeafsDelete { + res, err := r.clients[plan.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *VLANAccessMapResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state VLANAccessMap + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if _, ok := r.clients[state.Device.ValueString()]; !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + deleteMode := "all" + + if deleteMode == "all" { + res, err := r.clients[state.Device.ValueString()].DeleteData(state.Id.ValueString()) + if err != nil && res.StatusCode != 404 && res.StatusCode != 400 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } else { + deletePaths := state.getDeletePaths(ctx) + tflog.Debug(ctx, fmt.Sprintf("Paths to delete: %+v", deletePaths)) + + if YangPatch { + edits := []restconf.YangPatchEdit{} + for _, i := range deletePaths { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := r.clients[state.Device.ValueString()].YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } else { + for _, i := range deletePaths { + res, err := r.clients[state.Device.ValueString()].DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object, got error: %s", err)) + return + } + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +func (r *VLANAccessMapResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/resource_iosxe_vlan_access_map_test.go b/internal/provider/resource_iosxe_vlan_access_map_test.go new file mode 100644 index 00000000..3317d6b6 --- /dev/null +++ b/internal/provider/resource_iosxe_vlan_access_map_test.go @@ -0,0 +1,76 @@ +// Copyright © 2023 Cisco Systems, Inc. and its affiliates. +// All rights reserved. +// +// Licensed under the Mozilla Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://mozilla.org/MPL/2.0/ +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: MPL-2.0 + +// Code generated by "gen/generator.go"; DO NOT EDIT. + +package provider + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccIosxeVLANAccessMap(t *testing.T) { + if os.Getenv("C9000V") == "" { + t.Skip("skipping test, set environment variable C9000V") + } + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_access_map.test", "name", "accessmap1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_access_map.test", "value", "1000")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_access_map.test", "match_ipv6_address.0", "ipv6_address1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_access_map.test", "match_ip_address.0", "ip_address1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_access_map.test", "action", "forward")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccIosxeVLANAccessMapConfig_minimum(), + }, + { + Config: testAccIosxeVLANAccessMapConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + ResourceName: "iosxe_vlan_access_map.test", + ImportState: true, + ImportStateId: "Cisco-IOS-XE-native:native/vlan/Cisco-IOS-XE-vlan:access-map=accessmap1,1000", + }, + }, + }) +} + +func testAccIosxeVLANAccessMapConfig_minimum() string { + config := `resource "iosxe_vlan_access_map" "test" {` + "\n" + config += ` name = "accessmap1"` + "\n" + config += ` value = 1000` + "\n" + config += `}` + "\n" + return config +} + +func testAccIosxeVLANAccessMapConfig_all() string { + config := `resource "iosxe_vlan_access_map" "test" {` + "\n" + config += ` name = "accessmap1"` + "\n" + config += ` value = 1000` + "\n" + config += ` match_ipv6_address = ["ipv6_address1"]` + "\n" + config += ` match_ip_address = ["ip_address1"]` + "\n" + config += ` action = "forward"` + "\n" + config += `}` + "\n" + return config +} diff --git a/internal/provider/resource_iosxe_vlan_configuration_test.go b/internal/provider/resource_iosxe_vlan_configuration_test.go index 1905da68..176593f9 100644 --- a/internal/provider/resource_iosxe_vlan_configuration_test.go +++ b/internal/provider/resource_iosxe_vlan_configuration_test.go @@ -32,8 +32,6 @@ func TestAccIosxeVLANConfiguration(t *testing.T) { } var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_configuration.test", "vlan_id", "123")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_configuration.test", "evpn_instance", "123")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_vlan_configuration.test", "evpn_instance_vni", "10123")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -64,8 +62,6 @@ func testAccIosxeVLANConfigurationConfig_minimum() string { func testAccIosxeVLANConfigurationConfig_all() string { config := `resource "iosxe_vlan_configuration" "test" {` + "\n" config += ` vlan_id = 123` + "\n" - config += ` evpn_instance = 123` + "\n" - config += ` evpn_instance_vni = 10123` + "\n" config += `}` + "\n" return config } diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 33853c1b..022d03f0 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -69,6 +69,10 @@ description: |- - BREAKING CHANGE: Rename `summary_address` attribute to `summary_addresses` of `iosxe_ospf` resource and data source - Add `ipv4_unicast_networks_mask` and `ipv4_unicast_networks` attribute to `iosxe_bgp_address_family_ipv4` and `iosxe_bgp_address_family_ipv4_vrf` resources and data sources - Add `ipv6_unicast_networks` attribute to `iosxe_bgp_address_family_ipv6` and `iosxe_bgp_address_family_ipv6_vrf` resources and data sources +- Add `iosxe_service_template` resource and data source +- Add `iosxe_vlan_access_map` resource and data source +- Add `service_policy_subscriber` attribute to `iosxe_template` resources and data sources + ## 0.3.3