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