From 69087697b6db3777a51481418b0244a5b3575d99 Mon Sep 17 00:00:00 2001 From: Jeremy Spencer Date: Thu, 6 Nov 2025 22:34:47 -0500 Subject: [PATCH] feat: Implement Advanced BGP Support in Terraform Provider --- CHANGELOG.md | 5 +- docs/data-sources/bgp.md | 2 + docs/resources/bgp.md | 6 +- examples/resources/iosxe_bgp/resource.tf | 3 +- gen/definitions/bgp.yaml | 6 ++ internal/provider/data_source_iosxe_bgp.go | 8 +++ .../provider/data_source_iosxe_bgp_test.go | 6 +- internal/provider/model_iosxe_bgp.go | 57 +++++++++++++++++++ internal/provider/resource_iosxe_bgp.go | 11 ++++ internal/provider/resource_iosxe_bgp_test.go | 6 +- 10 files changed, 102 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae6c484e..8b12508b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add `ip_ssh_bulk_mode` and `ip_ssh_bulk_mode_window_size` attributes to `iosxe_system` resource and data source - Add `set_ip_next_hop_unchanged` attribute to `iosxe_route_map` resource and data source - Enhance `set_communities` attribute documentation in `iosxe_route_map` to clarify support for well-known BGP community values (internet, local-AS, no-advertise, no-export, gshut) +- Add `router_id_ip`, `bgp_graceful_restart`, and `bgp_update_delay` to `iosxe_bgp` resource and data source ## 0.9.3 @@ -21,7 +22,7 @@ - Add `passive_interface_disable_*` attributes to `iosxe_ospf` and `iosxe_ospf_vrf` resources and data sources - Fix issue with destroying `iosxe_interface_ethernet` resources - Change route target attributes of `iosxe_vrf` from type "List" to "Set" -- Add `role_based_enforcement` attributes to `iosxe_cts` +- Add `role_based_enforcement` attributes to `iosxe_cts` - Add `tftp_source_interface_*` attributes to `iosxe_system` resource and data source - Add `hash` attribute to `iosxe_crypto_pki` resource and data source - Add `snooping_information_option`, `snooping_information_option_allow_untrusted` and `snooping_information_option_format_remote_id_string` attributes to `iosxe_dhcp` resource and data source @@ -66,7 +67,7 @@ - Add `level`, `list_name`, `action_type`, `broadcast`, `group_broadcast`, `group_logger`, `group1_group`, `group2_group`, `group3_group`, `group4_group`, `name`, `default`, `none`, `start_stop_broadcast`, `start_stop_logger`, `start_stop_group1`, `start_stop_group2`, `start_stop_group3`, `start_stop_group4`, `stop_only_broadcast`, `stop_only_logger`, `stop_only_group1`, `stop_only_group2`, `stop_only_group3`, `stop_only_group4`, `wait_start_broadcast`, `wait_start_logger`, `wait_start_group1`, `wait_start_group2`, `wait_start_group3`, `wait_start_group4`, `name`, `none`, `start_stop_broadcast`, `start_stop_logger`, `start_stop_group1`, `start_stop_group2`, `start_stop_group3`, `start_stop_group4`, `stop_only_broadcast`, `stop_only_logger`, `stop_only_group1`, `stop_only_group2`, `stop_only_group3`, `stop_only_group4`, `wait_start_broadcast`, `wait_start_logger`, `wait_start_group1`, `wait_start_group2`, `wait_start_group3`, and `wait_start_group4` attributes to `iosxe_aaa_accounting` resource and data source - Add `enable_default_group1_cache`, `enable_default_group1_enable`, `enable_default_group1_group`, `enable_default_group1_line`, `enable_default_group1_none`, `enable_default_group2_cache`, `enable_default_group2_enable`, `enable_default_group2_group`, `enable_default_group2_line`, `enable_default_group2_none`, `enable_default_group3_cache`, `enable_default_group3_enable`, `enable_default_group3_group`, `enable_default_group3_line`, `enable_default_group3_none`, `enable_default_group4_cache`, `enable_default_group4_enable`, `enable_default_group4_group`, `enable_default_group4_line`, and `enable_default_group4_none` attributes to `iosxe_aaa_authentication` resource and data source - Add `level`, `list_name`, `a1_group`, `a1_local`, `a1_if_authenticated`, `a1_none`, `a1_radius`, `a1_tacacs`, `a2_group`, `a2_local`, `a2_if_authenticated`, `a2_none`, `a2_radius`, `a2_tacacs`, `a3_group`, `a3_local`, `a3_if_authenticated`, `a3_none`, `a3_radius`, `a3_tacacs`, `a4_group`, `a4_local`, `a4_if_authenticated`, `a4_none`, `a4_radius`, `a4_tacacs`, `name`, `group1_cache`, `group1_group`, `group1_radius`, and `group1_tacacs` attributes to `iosxe_aaa_authorization` resource and data source -- Add `vrf`, `local_authentication_type`, `local_authorization`, and `local_auth_max_fail_attempts` attributes to `iosxe_aaa` resrouce and data source +- Add `vrf`, `local_authentication_type`, `local_authorization`, and `local_auth_max_fail_attempts` attributes to `iosxe_aaa` resrouce and data source - Add `icmp_named_msg_type`, `destination_port_equal_2`, `destination_port_equal_3`, `destination_port_equal_4`, `destination_port_equal_5`, `destination_port_equal_6`, `destination_port_equal_7`, `destination_port_equal_8`, `destination_port_equal_9`, `destination_port_equal_10`, `icmp_msg_type`, and `icmp_msg_code` attributes to `iosxe_access_list_extended` resource and data source - Add `iosxe_access_list_role_based` resource and data source - Add `filter_lists_cdp` attribute to `iosxe_device_sensor` resource and data source diff --git a/docs/data-sources/bgp.md b/docs/data-sources/bgp.md index 11180d295..3b6ca7aaf 100644 --- a/docs/data-sources/bgp.md +++ b/docs/data-sources/bgp.md @@ -31,6 +31,8 @@ data "iosxe_bgp" "example" { ### Read-Only +- `bgp_graceful_restart` (Boolean) Graceful restart capability parameters +- `bgp_update_delay` (Number) Set the max initial delay for sending update - `default_ipv4_unicast` (Boolean) Activate ipv4-unicast for a peer by default - `id` (String) The path of the retrieved object. - `log_neighbor_changes` (Boolean) Log neighbor up/down and reset reason diff --git a/docs/resources/bgp.md b/docs/resources/bgp.md index 75f115d29..20958bf59 100644 --- a/docs/resources/bgp.md +++ b/docs/resources/bgp.md @@ -17,8 +17,9 @@ resource "iosxe_bgp" "example" { asn = "65000" default_ipv4_unicast = false log_neighbor_changes = true - router_id_loopback = 100 router_id_ip = "172.16.255.1" + bgp_graceful_restart = true + bgp_update_delay = 200 } ``` @@ -31,6 +32,9 @@ resource "iosxe_bgp" "example" { ### Optional +- `bgp_graceful_restart` (Boolean) Graceful restart capability parameters +- `bgp_update_delay` (Number) Set the max initial delay for sending update + - Range: `1`-`3600` - `default_ipv4_unicast` (Boolean) Activate ipv4-unicast for a peer by default - `delete_mode` (String) Configure behavior when deleting/destroying the resource. Either delete the entire object (YANG container) being managed, or only delete the individual resource attributes configured explicitly and leave everything else as-is. Default value is `all`. - Choices: `all`, `attributes` diff --git a/examples/resources/iosxe_bgp/resource.tf b/examples/resources/iosxe_bgp/resource.tf index 7f4337b8b..d0d0278e7 100644 --- a/examples/resources/iosxe_bgp/resource.tf +++ b/examples/resources/iosxe_bgp/resource.tf @@ -2,6 +2,7 @@ resource "iosxe_bgp" "example" { asn = "65000" default_ipv4_unicast = false log_neighbor_changes = true - router_id_loopback = 100 router_id_ip = "172.16.255.1" + bgp_graceful_restart = true + bgp_update_delay = 200 } diff --git a/gen/definitions/bgp.yaml b/gen/definitions/bgp.yaml index 189f6c17c..aa90cb4ae 100644 --- a/gen/definitions/bgp.yaml +++ b/gen/definitions/bgp.yaml @@ -16,10 +16,16 @@ attributes: xpath: bgp/router-id/interface/Loopback tf_name: router_id_loopback example: 100 + exclude_test: true - yang_name: bgp/router-id/id-choice/ip-id/ip-id xpath: bgp/router-id/ip-id tf_name: router_id_ip example: 172.16.255.1 + - yang_name: bgp/gr-options/graceful-restart + tf_name: bgp_graceful_restart + example: true + - yang_name: bgp/update-delay + example: 200 test_prerequisites: - path: Cisco-IOS-XE-native:native/interface/Loopback=100 attributes: diff --git a/internal/provider/data_source_iosxe_bgp.go b/internal/provider/data_source_iosxe_bgp.go index 1ffc37796..947e0f129 100644 --- a/internal/provider/data_source_iosxe_bgp.go +++ b/internal/provider/data_source_iosxe_bgp.go @@ -87,6 +87,14 @@ func (d *BGPDataSource) Schema(ctx context.Context, req datasource.SchemaRequest MarkdownDescription: "Manually configured router identifier", Computed: true, }, + "bgp_graceful_restart": schema.BoolAttribute{ + MarkdownDescription: "Graceful restart capability parameters", + Computed: true, + }, + "bgp_update_delay": schema.Int64Attribute{ + MarkdownDescription: "Set the max initial delay for sending update", + Computed: true, + }, }, } } diff --git a/internal/provider/data_source_iosxe_bgp_test.go b/internal/provider/data_source_iosxe_bgp_test.go index 951c836fb..e10c77fb1 100644 --- a/internal/provider/data_source_iosxe_bgp_test.go +++ b/internal/provider/data_source_iosxe_bgp_test.go @@ -34,8 +34,9 @@ func TestAccDataSourceIosxeBGP(t *testing.T) { var checks []resource.TestCheckFunc checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "default_ipv4_unicast", "false")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "log_neighbor_changes", "true")) - checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "router_id_loopback", "100")) checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "router_id_ip", "172.16.255.1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "bgp_graceful_restart", "true")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp.test", "bgp_update_delay", "200")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -73,8 +74,9 @@ func testAccDataSourceIosxeBGPConfig() string { config += ` asn = "65000"` + "\n" config += ` default_ipv4_unicast = false` + "\n" config += ` log_neighbor_changes = true` + "\n" - config += ` router_id_loopback = 100` + "\n" config += ` router_id_ip = "172.16.255.1"` + "\n" + config += ` bgp_graceful_restart = true` + "\n" + config += ` bgp_update_delay = 200` + "\n" config += ` depends_on = [iosxe_restconf.PreReq0, ]` + "\n" config += `}` + "\n" diff --git a/internal/provider/model_iosxe_bgp.go b/internal/provider/model_iosxe_bgp.go index d81a0ceef..1379073c8 100644 --- a/internal/provider/model_iosxe_bgp.go +++ b/internal/provider/model_iosxe_bgp.go @@ -45,6 +45,8 @@ type BGP struct { LogNeighborChanges types.Bool `tfsdk:"log_neighbor_changes"` RouterIdLoopback types.Int64 `tfsdk:"router_id_loopback"` RouterIdIp types.String `tfsdk:"router_id_ip"` + BgpGracefulRestart types.Bool `tfsdk:"bgp_graceful_restart"` + BgpUpdateDelay types.Int64 `tfsdk:"bgp_update_delay"` } type BGPData struct { @@ -55,6 +57,8 @@ type BGPData struct { LogNeighborChanges types.Bool `tfsdk:"log_neighbor_changes"` RouterIdLoopback types.Int64 `tfsdk:"router_id_loopback"` RouterIdIp types.String `tfsdk:"router_id_ip"` + BgpGracefulRestart types.Bool `tfsdk:"bgp_graceful_restart"` + BgpUpdateDelay types.Int64 `tfsdk:"bgp_update_delay"` } // End of section. //template:end types @@ -101,6 +105,14 @@ func (data BGP) toBody(ctx context.Context) string { if !data.RouterIdIp.IsNull() && !data.RouterIdIp.IsUnknown() { body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"bgp.router-id.ip-id", data.RouterIdIp.ValueString()) } + if !data.BgpGracefulRestart.IsNull() && !data.BgpGracefulRestart.IsUnknown() { + if data.BgpGracefulRestart.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"bgp.gr-options.graceful-restart", map[string]string{}) + } + } + if !data.BgpUpdateDelay.IsNull() && !data.BgpUpdateDelay.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"bgp.update-delay", strconv.FormatInt(data.BgpUpdateDelay.ValueInt64(), 10)) + } return body } @@ -142,6 +154,20 @@ func (data *BGP) updateFromBody(ctx context.Context, res gjson.Result) { } else { data.RouterIdIp = types.StringNull() } + if value := res.Get(prefix + "bgp.gr-options.graceful-restart"); !data.BgpGracefulRestart.IsNull() { + if value.Exists() { + data.BgpGracefulRestart = types.BoolValue(true) + } else { + data.BgpGracefulRestart = types.BoolValue(false) + } + } else { + data.BgpGracefulRestart = types.BoolNull() + } + if value := res.Get(prefix + "bgp.update-delay"); value.Exists() && !data.BgpUpdateDelay.IsNull() { + data.BgpUpdateDelay = types.Int64Value(value.Int()) + } else { + data.BgpUpdateDelay = types.Int64Null() + } } // End of section. //template:end updateFromBody @@ -169,6 +195,14 @@ func (data *BGP) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get(prefix + "bgp.router-id.ip-id"); value.Exists() { data.RouterIdIp = types.StringValue(value.String()) } + if value := res.Get(prefix + "bgp.gr-options.graceful-restart"); value.Exists() { + data.BgpGracefulRestart = types.BoolValue(true) + } else { + data.BgpGracefulRestart = types.BoolValue(false) + } + if value := res.Get(prefix + "bgp.update-delay"); value.Exists() { + data.BgpUpdateDelay = types.Int64Value(value.Int()) + } } // End of section. //template:end fromBody @@ -196,6 +230,14 @@ func (data *BGPData) fromBody(ctx context.Context, res gjson.Result) { if value := res.Get(prefix + "bgp.router-id.ip-id"); value.Exists() { data.RouterIdIp = types.StringValue(value.String()) } + if value := res.Get(prefix + "bgp.gr-options.graceful-restart"); value.Exists() { + data.BgpGracefulRestart = types.BoolValue(true) + } else { + data.BgpGracefulRestart = types.BoolValue(false) + } + if value := res.Get(prefix + "bgp.update-delay"); value.Exists() { + data.BgpUpdateDelay = types.Int64Value(value.Int()) + } } // End of section. //template:end fromBodyData @@ -204,6 +246,12 @@ func (data *BGPData) fromBody(ctx context.Context, res gjson.Result) { func (data *BGP) getDeletedItems(ctx context.Context, state BGP) []string { deletedItems := make([]string, 0) + if !state.BgpUpdateDelay.IsNull() && data.BgpUpdateDelay.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/bgp/update-delay", state.getPath())) + } + if !state.BgpGracefulRestart.IsNull() && data.BgpGracefulRestart.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/bgp/gr-options/graceful-restart", state.getPath())) + } if !state.RouterIdIp.IsNull() && data.RouterIdIp.IsNull() { deletedItems = append(deletedItems, fmt.Sprintf("%v/bgp/router-id/ip-id", state.getPath())) } @@ -226,6 +274,9 @@ func (data *BGP) getDeletedItems(ctx context.Context, state BGP) []string { func (data *BGP) getEmptyLeafsDelete(ctx context.Context) []string { emptyLeafsDelete := make([]string, 0) + if !data.BgpGracefulRestart.IsNull() && !data.BgpGracefulRestart.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/bgp/gr-options/graceful-restart", data.getPath())) + } return emptyLeafsDelete } @@ -236,6 +287,12 @@ func (data *BGP) getEmptyLeafsDelete(ctx context.Context) []string { func (data *BGP) getDeletePaths(ctx context.Context) []string { var deletePaths []string + if !data.BgpUpdateDelay.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/bgp/update-delay", data.getPath())) + } + if !data.BgpGracefulRestart.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/bgp/gr-options/graceful-restart", data.getPath())) + } if !data.RouterIdIp.IsNull() { deletePaths = append(deletePaths, fmt.Sprintf("%v/bgp/router-id/ip-id", data.getPath())) } diff --git a/internal/provider/resource_iosxe_bgp.go b/internal/provider/resource_iosxe_bgp.go index e55feeced..c2de3f869 100644 --- a/internal/provider/resource_iosxe_bgp.go +++ b/internal/provider/resource_iosxe_bgp.go @@ -115,6 +115,17 @@ func (r *BGPResource) Schema(ctx context.Context, req resource.SchemaRequest, re stringvalidator.RegexMatches(regexp.MustCompile(`(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(%[\p{N}\p{L}]+)?`), ""), }, }, + "bgp_graceful_restart": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Graceful restart capability parameters").String, + Optional: true, + }, + "bgp_update_delay": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("Set the max initial delay for sending update").AddIntegerRangeDescription(1, 3600).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 3600), + }, + }, }, } } diff --git a/internal/provider/resource_iosxe_bgp_test.go b/internal/provider/resource_iosxe_bgp_test.go index e7f241123..db7bda0a1 100644 --- a/internal/provider/resource_iosxe_bgp_test.go +++ b/internal/provider/resource_iosxe_bgp_test.go @@ -37,8 +37,9 @@ func TestAccIosxeBGP(t *testing.T) { checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "asn", "65000")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "default_ipv4_unicast", "false")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "log_neighbor_changes", "true")) - checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "router_id_loopback", "100")) checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "router_id_ip", "172.16.255.1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "bgp_graceful_restart", "true")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp.test", "bgp_update_delay", "200")) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -111,8 +112,9 @@ func testAccIosxeBGPConfig_all() string { config += ` asn = "65000"` + "\n" config += ` default_ipv4_unicast = false` + "\n" config += ` log_neighbor_changes = true` + "\n" - config += ` router_id_loopback = 100` + "\n" config += ` router_id_ip = "172.16.255.1"` + "\n" + config += ` bgp_graceful_restart = true` + "\n" + config += ` bgp_update_delay = 200` + "\n" config += ` depends_on = [iosxe_restconf.PreReq0, ]` + "\n" config += `}` + "\n" return config