diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ae6c484e..1a35d2945 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 `iosxe_bgp_template_peer_policy` resource ## 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_template_peer_policy.md b/docs/data-sources/bgp_template_peer_policy.md new file mode 100644 index 000000000..71ba2d6d2 --- /dev/null +++ b/docs/data-sources/bgp_template_peer_policy.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_bgp_template_peer_policy Data Source - terraform-provider-iosxe" +subcategory: "BGP" +description: |- + This data source can read the BGP Template Peer Policy configuration. +--- + +# iosxe_bgp_template_peer_policy (Data Source) + +This data source can read the BGP Template Peer Policy configuration. + +## Example Usage + +```terraform +data "iosxe_bgp_template_peer_policy" "example" { + asn = "65000" + name = "PEERPOLICY_1" +} +``` + + +## Schema + +### Required + +- `asn` (String) +- `name` (String) + +### Optional + +- `device` (String) A device name from the provider configuration. + +### Read-Only + +- `allowas_in_as_number` (Number) +- `as_override_split_horizon` (Boolean) +- `id` (String) The path of the retrieved object. +- `route_maps` (Attributes List) Apply route map to neighbor (see [below for nested schema](#nestedatt--route_maps)) +- `route_reflector_client` (Boolean) Configure a neighbor as Route Reflector client +- `send_community` (String) + + +### Nested Schema for `route_maps` + +Read-Only: + +- `in_out` (String) +- `route_map_name` (String) diff --git a/docs/guides/changelog.md b/docs/guides/changelog.md index 7fa3bd4df..517dfc963 100644 --- a/docs/guides/changelog.md +++ b/docs/guides/changelog.md @@ -16,6 +16,7 @@ description: |- - 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 `iosxe_bgp_template_peer_policy` resource ## 0.9.3 @@ -30,7 +31,7 @@ description: |- - 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 @@ -75,7 +76,7 @@ description: |- - 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/resources/bgp_template_peer_policy.md b/docs/resources/bgp_template_peer_policy.md new file mode 100644 index 000000000..113870420 --- /dev/null +++ b/docs/resources/bgp_template_peer_policy.md @@ -0,0 +1,71 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "iosxe_bgp_template_peer_policy Resource - terraform-provider-iosxe" +subcategory: "BGP" +description: |- + This resource can manage the BGP Template Peer Policy configuration. +--- + +# iosxe_bgp_template_peer_policy (Resource) + +This resource can manage the BGP Template Peer Policy configuration. + +## Example Usage + +```terraform +resource "iosxe_bgp_template_peer_policy" "example" { + asn = "65000" + name = "PEERPOLICY_1" + route_reflector_client = true + send_community = "both" + route_maps = [ + { + in_out = "in" + route_map_name = "ROUTEMAP_1" + } + ] + allowas_in_as_number = 2 + as_override_split_horizon = true +} +``` + + +## Schema + +### Required + +- `asn` (String) +- `name` (String) + +### Optional + +- `allowas_in_as_number` (Number) - Range: `1`-`10` +- `as_override_split_horizon` (Boolean) +- `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` +- `device` (String) A device name from the provider configuration. +- `route_maps` (Attributes List) Apply route map to neighbor (see [below for nested schema](#nestedatt--route_maps)) +- `route_reflector_client` (Boolean) Configure a neighbor as Route Reflector client +- `send_community` (String) - Choices: `both`, `extended`, `standard` + +### Read-Only + +- `id` (String) The path of the object. + + +### Nested Schema for `route_maps` + +Required: + +- `in_out` (String) - Choices: `in`, `out` +- `route_map_name` (String) + +## Import + +Import is supported using the following syntax: + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + +```shell +terraform import iosxe_bgp_template_peer_policy.example "," +``` diff --git a/examples/data-sources/iosxe_bgp_template_peer_policy/data-source.tf b/examples/data-sources/iosxe_bgp_template_peer_policy/data-source.tf new file mode 100644 index 000000000..ee67e9d5e --- /dev/null +++ b/examples/data-sources/iosxe_bgp_template_peer_policy/data-source.tf @@ -0,0 +1,4 @@ +data "iosxe_bgp_template_peer_policy" "example" { + asn = "65000" + name = "PEERPOLICY_1" +} diff --git a/examples/resources/iosxe_bgp_template_peer_policy/import.sh b/examples/resources/iosxe_bgp_template_peer_policy/import.sh new file mode 100644 index 000000000..b2c3190da --- /dev/null +++ b/examples/resources/iosxe_bgp_template_peer_policy/import.sh @@ -0,0 +1 @@ +terraform import iosxe_bgp_template_peer_policy.example "," diff --git a/examples/resources/iosxe_bgp_template_peer_policy/resource.tf b/examples/resources/iosxe_bgp_template_peer_policy/resource.tf new file mode 100644 index 000000000..180fc1e3d --- /dev/null +++ b/examples/resources/iosxe_bgp_template_peer_policy/resource.tf @@ -0,0 +1,14 @@ +resource "iosxe_bgp_template_peer_policy" "example" { + asn = "65000" + name = "PEERPOLICY_1" + route_reflector_client = true + send_community = "both" + route_maps = [ + { + in_out = "in" + route_map_name = "ROUTEMAP_1" + } + ] + allowas_in_as_number = 2 + as_override_split_horizon = true +} diff --git a/gen/definitions/bgp_template_peer_policy.yaml b/gen/definitions/bgp_template_peer_policy.yaml new file mode 100644 index 000000000..1857e48c2 --- /dev/null +++ b/gen/definitions/bgp_template_peer_policy.yaml @@ -0,0 +1,40 @@ +--- +name: BGP Template Peer Policy +path: Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=%v/template/peer-policy=%v +doc_category: BGP +attributes: + - yang_name: id + tf_name: asn + example: 65000 + - yang_name: name + example: PEERPOLICY_1 + - yang_name: route-reflector-client + example: true + - yang_name: send-community/send-community-where + tf_name: send_community + example: both + - yang_name: route-map1 + type: List + tf_name: route_maps + attributes: + - yang_name: inout + tf_name: in_out + id: true + example: in + - yang_name: route-map-name + example: ROUTEMAP_1 + - yang_name: allowas-in/as-number + tf_name: allowas_in_as_number + example: 2 + - yang_name: as-override/split-horizon + tf_name: as_override_split_horizon + example: true +test_prerequisites: + - path: Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=65000 + attributes: + - name: id + value: 65000 + - path: Cisco-IOS-XE-native:native/route-map=ROUTEMAP_1 + attributes: + - name: name + value: ROUTEMAP_1 diff --git a/internal/provider/data_source_iosxe_bgp_template_peer_policy.go b/internal/provider/data_source_iosxe_bgp_template_peer_policy.go new file mode 100644 index 000000000..6603ad9a0 --- /dev/null +++ b/internal/provider/data_source_iosxe_bgp_template_peer_policy.go @@ -0,0 +1,164 @@ +// 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 + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +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" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &BGPTemplatePeerPolicyDataSource{} + _ datasource.DataSourceWithConfigure = &BGPTemplatePeerPolicyDataSource{} +) + +func NewBGPTemplatePeerPolicyDataSource() datasource.DataSource { + return &BGPTemplatePeerPolicyDataSource{} +} + +type BGPTemplatePeerPolicyDataSource struct { + data *IosxeProviderData +} + +func (d *BGPTemplatePeerPolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_bgp_template_peer_policy" +} + +func (d *BGPTemplatePeerPolicyDataSource) 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 BGP Template Peer Policy 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, + }, + "asn": schema.StringAttribute{ + MarkdownDescription: "", + Required: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "", + Required: true, + }, + "route_reflector_client": schema.BoolAttribute{ + MarkdownDescription: "Configure a neighbor as Route Reflector client", + Computed: true, + }, + "send_community": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "route_maps": schema.ListNestedAttribute{ + MarkdownDescription: "Apply route map to neighbor", + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "in_out": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + "route_map_name": schema.StringAttribute{ + MarkdownDescription: "", + Computed: true, + }, + }, + }, + }, + "allowas_in_as_number": schema.Int64Attribute{ + MarkdownDescription: "", + Computed: true, + }, + "as_override_split_horizon": schema.BoolAttribute{ + MarkdownDescription: "", + Computed: true, + }, + }, + } +} + +func (d *BGPTemplatePeerPolicyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.data = req.ProviderData.(*IosxeProviderData) +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (d *BGPTemplatePeerPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config BGPTemplatePeerPolicyData + + // Read config + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", config.getPath())) + + device, ok := d.data.Devices[config.Device.ValueString()] + if !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", config.Device.ValueString())) + return + } + + res, err := device.Client.GetData(config.getPath()) + if res.StatusCode == 404 { + config = BGPTemplatePeerPolicyData{Device: config.Device} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (%s), got error: %s", config.getPath(), 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...) +} + +// End of section. //template:end read diff --git a/internal/provider/data_source_iosxe_bgp_template_peer_policy_test.go b/internal/provider/data_source_iosxe_bgp_template_peer_policy_test.go new file mode 100644 index 000000000..baf664758 --- /dev/null +++ b/internal/provider/data_source_iosxe_bgp_template_peer_policy_test.go @@ -0,0 +1,103 @@ +// 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 + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSource + +func TestAccDataSourceIosxeBGPTemplatePeerPolicy(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "route_reflector_client", "true")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "send_community", "both")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "route_maps.0.in_out", "in")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "route_maps.0.route_map_name", "ROUTEMAP_1")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "allowas_in_as_number", "2")) + checks = append(checks, resource.TestCheckResourceAttr("data.iosxe_bgp_template_peer_policy.test", "as_override_split_horizon", "true")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceIosxeBGPTemplatePeerPolicyPrerequisitesConfig + testAccDataSourceIosxeBGPTemplatePeerPolicyConfig(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAccDataSource + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccDataSourceIosxeBGPTemplatePeerPolicyPrerequisitesConfig = ` +resource "iosxe_restconf" "PreReq0" { + path = "Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=65000" + attributes = { + "id" = "65000" + } +} + +resource "iosxe_restconf" "PreReq1" { + path = "Cisco-IOS-XE-native:native/route-map=ROUTEMAP_1" + attributes = { + "name" = "ROUTEMAP_1" + } +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccDataSourceConfig + +func testAccDataSourceIosxeBGPTemplatePeerPolicyConfig() string { + config := `resource "iosxe_bgp_template_peer_policy" "test" {` + "\n" + config += ` delete_mode = "attributes"` + "\n" + config += ` asn = "65000"` + "\n" + config += ` name = "PEERPOLICY_1"` + "\n" + config += ` route_reflector_client = true` + "\n" + config += ` send_community = "both"` + "\n" + config += ` route_maps = [{` + "\n" + config += ` in_out = "in"` + "\n" + config += ` route_map_name = "ROUTEMAP_1"` + "\n" + config += ` }]` + "\n" + config += ` allowas_in_as_number = 2` + "\n" + config += ` as_override_split_horizon = true` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, iosxe_restconf.PreReq1, ]` + "\n" + config += `}` + "\n" + + config += ` + data "iosxe_bgp_template_peer_policy" "test" { + asn = "65000" + name = "PEERPOLICY_1" + depends_on = [iosxe_bgp_template_peer_policy.test] + } + ` + return config +} + +// End of section. //template:end testAccDataSourceConfig diff --git a/internal/provider/model_iosxe_bgp_template_peer_policy.go b/internal/provider/model_iosxe_bgp_template_peer_policy.go new file mode 100644 index 000000000..e4a8a6bbb --- /dev/null +++ b/internal/provider/model_iosxe_bgp_template_peer_policy.go @@ -0,0 +1,386 @@ +// 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 + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +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" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin types +type BGPTemplatePeerPolicy struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + DeleteMode types.String `tfsdk:"delete_mode"` + Asn types.String `tfsdk:"asn"` + Name types.String `tfsdk:"name"` + RouteReflectorClient types.Bool `tfsdk:"route_reflector_client"` + SendCommunity types.String `tfsdk:"send_community"` + RouteMaps []BGPTemplatePeerPolicyRouteMaps `tfsdk:"route_maps"` + AllowasInAsNumber types.Int64 `tfsdk:"allowas_in_as_number"` + AsOverrideSplitHorizon types.Bool `tfsdk:"as_override_split_horizon"` +} + +type BGPTemplatePeerPolicyData struct { + Device types.String `tfsdk:"device"` + Id types.String `tfsdk:"id"` + Asn types.String `tfsdk:"asn"` + Name types.String `tfsdk:"name"` + RouteReflectorClient types.Bool `tfsdk:"route_reflector_client"` + SendCommunity types.String `tfsdk:"send_community"` + RouteMaps []BGPTemplatePeerPolicyRouteMaps `tfsdk:"route_maps"` + AllowasInAsNumber types.Int64 `tfsdk:"allowas_in_as_number"` + AsOverrideSplitHorizon types.Bool `tfsdk:"as_override_split_horizon"` +} +type BGPTemplatePeerPolicyRouteMaps struct { + InOut types.String `tfsdk:"in_out"` + RouteMapName types.String `tfsdk:"route_map_name"` +} + +// End of section. //template:end types + +// Section below is generated&owned by "gen/generator.go". //template:begin getPath + +func (data BGPTemplatePeerPolicy) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=%v/template/peer-policy=%v", url.QueryEscape(fmt.Sprintf("%v", data.Asn.ValueString())), url.QueryEscape(fmt.Sprintf("%v", data.Name.ValueString()))) +} + +func (data BGPTemplatePeerPolicyData) getPath() string { + return fmt.Sprintf("Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=%v/template/peer-policy=%v", url.QueryEscape(fmt.Sprintf("%v", data.Asn.ValueString())), url.QueryEscape(fmt.Sprintf("%v", data.Name.ValueString()))) +} + +// if last path element has a key -> remove it +func (data BGPTemplatePeerPolicy) getPathShort() string { + path := data.getPath() + re := regexp.MustCompile(`(.*)=[^\/]*$`) + matches := re.FindStringSubmatch(path) + if len(matches) <= 1 { + return path + } + return matches[1] +} + +// End of section. //template:end getPath + +// Section below is generated&owned by "gen/generator.go". //template:begin toBody + +func (data BGPTemplatePeerPolicy) 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.RouteReflectorClient.IsNull() && !data.RouteReflectorClient.IsUnknown() { + if data.RouteReflectorClient.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"route-reflector-client", map[string]string{}) + } + } + if !data.SendCommunity.IsNull() && !data.SendCommunity.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"send-community.send-community-where", data.SendCommunity.ValueString()) + } + if !data.AllowasInAsNumber.IsNull() && !data.AllowasInAsNumber.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"allowas-in.as-number", strconv.FormatInt(data.AllowasInAsNumber.ValueInt64(), 10)) + } + if !data.AsOverrideSplitHorizon.IsNull() && !data.AsOverrideSplitHorizon.IsUnknown() { + if data.AsOverrideSplitHorizon.ValueBool() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"as-override.split-horizon", map[string]string{}) + } + } + if len(data.RouteMaps) > 0 { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"route-map1", []interface{}{}) + for index, item := range data.RouteMaps { + if !item.InOut.IsNull() && !item.InOut.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"route-map1"+"."+strconv.Itoa(index)+"."+"inout", item.InOut.ValueString()) + } + if !item.RouteMapName.IsNull() && !item.RouteMapName.IsUnknown() { + body, _ = sjson.Set(body, helpers.LastElement(data.getPath())+"."+"route-map1"+"."+strconv.Itoa(index)+"."+"route-map-name", item.RouteMapName.ValueString()) + } + } + } + return body +} + +// End of section. //template:end toBody + +// Section below is generated&owned by "gen/generator.go". //template:begin updateFromBody + +func (data *BGPTemplatePeerPolicy) 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 + "route-reflector-client"); !data.RouteReflectorClient.IsNull() { + if value.Exists() { + data.RouteReflectorClient = types.BoolValue(true) + } else { + data.RouteReflectorClient = types.BoolValue(false) + } + } else { + data.RouteReflectorClient = types.BoolNull() + } + if value := res.Get(prefix + "send-community.send-community-where"); value.Exists() && !data.SendCommunity.IsNull() { + data.SendCommunity = types.StringValue(value.String()) + } else { + data.SendCommunity = types.StringNull() + } + for i := range data.RouteMaps { + keys := [...]string{"inout"} + keyValues := [...]string{data.RouteMaps[i].InOut.ValueString()} + + var r gjson.Result + res.Get(prefix + "route-map1").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("inout"); value.Exists() && !data.RouteMaps[i].InOut.IsNull() { + data.RouteMaps[i].InOut = types.StringValue(value.String()) + } else { + data.RouteMaps[i].InOut = types.StringNull() + } + if value := r.Get("route-map-name"); value.Exists() && !data.RouteMaps[i].RouteMapName.IsNull() { + data.RouteMaps[i].RouteMapName = types.StringValue(value.String()) + } else { + data.RouteMaps[i].RouteMapName = types.StringNull() + } + } + if value := res.Get(prefix + "allowas-in.as-number"); value.Exists() && !data.AllowasInAsNumber.IsNull() { + data.AllowasInAsNumber = types.Int64Value(value.Int()) + } else { + data.AllowasInAsNumber = types.Int64Null() + } + if value := res.Get(prefix + "as-override.split-horizon"); !data.AsOverrideSplitHorizon.IsNull() { + if value.Exists() { + data.AsOverrideSplitHorizon = types.BoolValue(true) + } else { + data.AsOverrideSplitHorizon = types.BoolValue(false) + } + } else { + data.AsOverrideSplitHorizon = types.BoolNull() + } +} + +// End of section. //template:end updateFromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBody + +func (data *BGPTemplatePeerPolicy) 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 + "route-reflector-client"); value.Exists() { + data.RouteReflectorClient = types.BoolValue(true) + } else { + data.RouteReflectorClient = types.BoolValue(false) + } + if value := res.Get(prefix + "send-community.send-community-where"); value.Exists() { + data.SendCommunity = types.StringValue(value.String()) + } + if value := res.Get(prefix + "route-map1"); value.Exists() { + data.RouteMaps = make([]BGPTemplatePeerPolicyRouteMaps, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := BGPTemplatePeerPolicyRouteMaps{} + if cValue := v.Get("inout"); cValue.Exists() { + item.InOut = types.StringValue(cValue.String()) + } + if cValue := v.Get("route-map-name"); cValue.Exists() { + item.RouteMapName = types.StringValue(cValue.String()) + } + data.RouteMaps = append(data.RouteMaps, item) + return true + }) + } + if value := res.Get(prefix + "allowas-in.as-number"); value.Exists() { + data.AllowasInAsNumber = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "as-override.split-horizon"); value.Exists() { + data.AsOverrideSplitHorizon = types.BoolValue(true) + } else { + data.AsOverrideSplitHorizon = types.BoolValue(false) + } +} + +// End of section. //template:end fromBody + +// Section below is generated&owned by "gen/generator.go". //template:begin fromBodyData + +func (data *BGPTemplatePeerPolicyData) 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 + "route-reflector-client"); value.Exists() { + data.RouteReflectorClient = types.BoolValue(true) + } else { + data.RouteReflectorClient = types.BoolValue(false) + } + if value := res.Get(prefix + "send-community.send-community-where"); value.Exists() { + data.SendCommunity = types.StringValue(value.String()) + } + if value := res.Get(prefix + "route-map1"); value.Exists() { + data.RouteMaps = make([]BGPTemplatePeerPolicyRouteMaps, 0) + value.ForEach(func(k, v gjson.Result) bool { + item := BGPTemplatePeerPolicyRouteMaps{} + if cValue := v.Get("inout"); cValue.Exists() { + item.InOut = types.StringValue(cValue.String()) + } + if cValue := v.Get("route-map-name"); cValue.Exists() { + item.RouteMapName = types.StringValue(cValue.String()) + } + data.RouteMaps = append(data.RouteMaps, item) + return true + }) + } + if value := res.Get(prefix + "allowas-in.as-number"); value.Exists() { + data.AllowasInAsNumber = types.Int64Value(value.Int()) + } + if value := res.Get(prefix + "as-override.split-horizon"); value.Exists() { + data.AsOverrideSplitHorizon = types.BoolValue(true) + } else { + data.AsOverrideSplitHorizon = types.BoolValue(false) + } +} + +// End of section. //template:end fromBodyData + +// Section below is generated&owned by "gen/generator.go". //template:begin getDeletedItems + +func (data *BGPTemplatePeerPolicy) getDeletedItems(ctx context.Context, state BGPTemplatePeerPolicy) []string { + deletedItems := make([]string, 0) + if !state.AsOverrideSplitHorizon.IsNull() && data.AsOverrideSplitHorizon.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/as-override/split-horizon", state.getPath())) + } + if !state.AllowasInAsNumber.IsNull() && data.AllowasInAsNumber.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/allowas-in/as-number", state.getPath())) + } + for i := range state.RouteMaps { + stateKeyValues := [...]string{state.RouteMaps[i].InOut.ValueString()} + + emptyKeys := true + if !reflect.ValueOf(state.RouteMaps[i].InOut.ValueString()).IsZero() { + emptyKeys = false + } + if emptyKeys { + continue + } + + found := false + for j := range data.RouteMaps { + found = true + if state.RouteMaps[i].InOut.ValueString() != data.RouteMaps[j].InOut.ValueString() { + found = false + } + if found { + if !state.RouteMaps[i].RouteMapName.IsNull() && data.RouteMaps[j].RouteMapName.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/route-map1=%v/route-map-name", state.getPath(), strings.Join(stateKeyValues[:], ","))) + } + break + } + } + if !found { + deletedItems = append(deletedItems, fmt.Sprintf("%v/route-map1=%v", state.getPath(), strings.Join(stateKeyValues[:], ","))) + } + } + if !state.SendCommunity.IsNull() && data.SendCommunity.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/send-community/send-community-where", state.getPath())) + } + if !state.RouteReflectorClient.IsNull() && data.RouteReflectorClient.IsNull() { + deletedItems = append(deletedItems, fmt.Sprintf("%v/route-reflector-client", state.getPath())) + } + + return deletedItems +} + +// End of section. //template:end getDeletedItems + +// Section below is generated&owned by "gen/generator.go". //template:begin getEmptyLeafsDelete + +func (data *BGPTemplatePeerPolicy) getEmptyLeafsDelete(ctx context.Context) []string { + emptyLeafsDelete := make([]string, 0) + if !data.AsOverrideSplitHorizon.IsNull() && !data.AsOverrideSplitHorizon.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/as-override/split-horizon", data.getPath())) + } + + if !data.RouteReflectorClient.IsNull() && !data.RouteReflectorClient.ValueBool() { + emptyLeafsDelete = append(emptyLeafsDelete, fmt.Sprintf("%v/route-reflector-client", data.getPath())) + } + + return emptyLeafsDelete +} + +// End of section. //template:end getEmptyLeafsDelete + +// Section below is generated&owned by "gen/generator.go". //template:begin getDeletePaths + +func (data *BGPTemplatePeerPolicy) getDeletePaths(ctx context.Context) []string { + var deletePaths []string + if !data.AsOverrideSplitHorizon.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/as-override/split-horizon", data.getPath())) + } + if !data.AllowasInAsNumber.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/allowas-in/as-number", data.getPath())) + } + for i := range data.RouteMaps { + keyValues := [...]string{data.RouteMaps[i].InOut.ValueString()} + + deletePaths = append(deletePaths, fmt.Sprintf("%v/route-map1=%v", data.getPath(), strings.Join(keyValues[:], ","))) + } + if !data.SendCommunity.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/send-community/send-community-where", data.getPath())) + } + if !data.RouteReflectorClient.IsNull() { + deletePaths = append(deletePaths, fmt.Sprintf("%v/route-reflector-client", data.getPath())) + } + + return deletePaths +} + +// End of section. //template:end getDeletePaths diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 8c8ba8a87..8648656c4 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -391,6 +391,7 @@ func (p *IosxeProvider) Resources(ctx context.Context) []func() resource.Resourc NewBGPL2VPNEVPNNeighborResource, NewBGPNeighborResource, NewBGPPeerSessionTemplateResource, + NewBGPTemplatePeerPolicyResource, NewCDPResource, NewClassMapResource, NewClockResource, @@ -496,6 +497,7 @@ func (p *IosxeProvider) DataSources(ctx context.Context) []func() datasource.Dat NewBGPL2VPNEVPNNeighborDataSource, NewBGPNeighborDataSource, NewBGPPeerSessionTemplateDataSource, + NewBGPTemplatePeerPolicyDataSource, NewCDPDataSource, NewClassMapDataSource, NewClockDataSource, diff --git a/internal/provider/resource_iosxe_bgp_template_peer_policy.go b/internal/provider/resource_iosxe_bgp_template_peer_policy.go new file mode 100644 index 000000000..226d4014b --- /dev/null +++ b/internal/provider/resource_iosxe_bgp_template_peer_policy.go @@ -0,0 +1,462 @@ +// 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 + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "context" + "fmt" + "strings" + + "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" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin model + +// Ensure provider defined types fully satisfy framework interfaces +var ( + _ resource.Resource = &BGPTemplatePeerPolicyResource{} + _ resource.ResourceWithImportState = &BGPTemplatePeerPolicyResource{} +) + +func NewBGPTemplatePeerPolicyResource() resource.Resource { + return &BGPTemplatePeerPolicyResource{} +} + +type BGPTemplatePeerPolicyResource struct { + data *IosxeProviderData +} + +func (r *BGPTemplatePeerPolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_bgp_template_peer_policy" +} + +func (r *BGPTemplatePeerPolicyResource) 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 BGP Template Peer Policy 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(), + }, + }, + "delete_mode": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("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`.").AddStringEnumDescription("all", "attributes").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("all", "attributes"), + }, + }, + "asn": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "route_reflector_client": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Configure a neighbor as Route Reflector client").String, + Optional: true, + }, + "send_community": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").AddStringEnumDescription("both", "extended", "standard").String, + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("both", "extended", "standard"), + }, + }, + "route_maps": schema.ListNestedAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("Apply route map to neighbor").String, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "in_out": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").AddStringEnumDescription("in", "out").String, + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("in", "out"), + }, + }, + "route_map_name": schema.StringAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Required: true, + }, + }, + }, + }, + "allowas_in_as_number": schema.Int64Attribute{ + MarkdownDescription: helpers.NewAttributeDescription("").AddIntegerRangeDescription(1, 10).String, + Optional: true, + Validators: []validator.Int64{ + int64validator.Between(1, 10), + }, + }, + "as_override_split_horizon": schema.BoolAttribute{ + MarkdownDescription: helpers.NewAttributeDescription("").String, + Optional: true, + }, + }, + } +} + +func (r *BGPTemplatePeerPolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.data = req.ProviderData.(*IosxeProviderData) +} + +// End of section. //template:end model + +// Section below is generated&owned by "gen/generator.go". //template:begin create + +func (r *BGPTemplatePeerPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan BGPTemplatePeerPolicy + + // Read plan + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Create", plan.getPath())) + + device, ok := r.data.Devices[plan.Device.ValueString()] + if !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + if device.Managed { + // 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 := device.Client.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 := device.Client.PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = device.Client.PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH, %s), got error: %s", plan.getPathShort(), err)) + return + } + for _, i := range emptyLeafsDelete { + res, err := device.Client.DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (%s), got error: %s", i, 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...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end create + +// Section below is generated&owned by "gen/generator.go". //template:begin read + +func (r *BGPTemplatePeerPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state BGPTemplatePeerPolicy + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Read", state.Id.ValueString())) + + device, ok := r.data.Devices[state.Device.ValueString()] + if !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + if device.Managed { + res, err := device.Client.GetData(state.Id.ValueString()) + if res.StatusCode == 404 { + state = BGPTemplatePeerPolicy{Device: state.Device, Id: state.Id} + } else { + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to retrieve object (%s), got error: %s", state.Id.ValueString(), err)) + return + } + + imp, diags := helpers.IsFlagImporting(ctx, req) + if resp.Diagnostics.Append(diags...); resp.Diagnostics.HasError() { + return + } + + // After `terraform import` we switch to a full read. + if imp { + state.fromBody(ctx, res.Res) + } else { + 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...) + + helpers.SetFlagImporting(ctx, false, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end read + +// Section below is generated&owned by "gen/generator.go". //template:begin update + +func (r *BGPTemplatePeerPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state BGPTemplatePeerPolicy + + // 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 + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Update", plan.Id.ValueString())) + + device, ok := r.data.Devices[plan.Device.ValueString()] + if !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", plan.Device.ValueString())) + return + } + + if device.Managed { + body := plan.toBody(ctx) + + deletedItems := plan.getDeletedItems(ctx, state) + tflog.Debug(ctx, fmt.Sprintf("Removed items to delete: %+v", deletedItems)) + + emptyLeafsDelete := plan.getEmptyLeafsDelete(ctx) + tflog.Debug(ctx, fmt.Sprintf("List of empty leafs to delete: %+v", emptyLeafsDelete)) + + if YangPatch { + var edits []restconf.YangPatchEdit + for _, i := range deletedItems { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + edits = append(edits, restconf.NewYangPatchEdit("merge", plan.getPath(), restconf.Body{Str: body})) + for _, i := range emptyLeafsDelete { + edits = append(edits, restconf.NewYangPatchEdit("remove", i, restconf.Body{})) + } + _, err := device.Client.YangPatchData("", "1", "", edits) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to update object, got error: %s", err)) + return + } + } else { + for _, i := range deletedItems { + res, err := device.Client.DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (%s), got error: %s", i, err)) + return + } + } + res, err := device.Client.PatchData(plan.getPathShort(), body) + if len(res.Errors.Error) > 0 && res.Errors.Error[0].ErrorMessage == "patch to a nonexistent resource" { + _, err = device.Client.PutData(plan.getPath(), body) + } + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to configure object (PATCH, %s), got error: %s", plan.getPathShort(), err)) + return + } + for _, i := range emptyLeafsDelete { + res, err := device.Client.DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (%s), got error: %s", i, err)) + return + } + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Update finished successfully", plan.Id.ValueString())) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// End of section. //template:end update + +// Section below is generated&owned by "gen/generator.go". //template:begin delete + +func (r *BGPTemplatePeerPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state BGPTemplatePeerPolicy + + // Read state + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Beginning Delete", state.Id.ValueString())) + + device, ok := r.data.Devices[state.Device.ValueString()] + if !ok { + resp.Diagnostics.AddAttributeError(path.Root("device"), "Invalid device", fmt.Sprintf("Device '%s' does not exist in provider configuration.", state.Device.ValueString())) + return + } + + if device.Managed { + deleteMode := "all" + if state.DeleteMode.ValueString() == "all" { + deleteMode = "all" + } else if state.DeleteMode.ValueString() == "attributes" { + deleteMode = "attributes" + } + + if deleteMode == "all" { + res, err := device.Client.DeleteData(state.Id.ValueString()) + if err != nil && res.StatusCode != 404 && res.StatusCode != 400 { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Failed to delete object (%s), got error: %s", state.Id.ValueString(), 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 := device.Client.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 := device.Client.DeleteData(i) + if err != nil && res.StatusCode != 404 { + resp.Diagnostics.AddWarning("Client Warning", fmt.Sprintf("Failed to delete object (%s), got error: %s", i, err)) + } + } + } + } + } + + tflog.Debug(ctx, fmt.Sprintf("%s: Delete finished successfully", state.Id.ValueString())) + + resp.State.RemoveResource(ctx) +} + +// End of section. //template:end delete + +// Section below is generated&owned by "gen/generator.go". //template:begin import + +func (r *BGPTemplatePeerPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + idParts = helpers.RemoveEmptyStrings(idParts) + + if len(idParts) != 2 && len(idParts) != 3 { + expectedIdentifier := "Expected import identifier with format: ','" + expectedIdentifier += " or ',,'" + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("%s. Got: %q", expectedIdentifier, req.ID), + ) + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("asn"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[1])...) + if len(idParts) == 3 { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("device"), idParts[len(idParts)-1])...) + } + + // construct path for 'id' attribute + var state BGPTemplatePeerPolicy + diags := resp.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), state.getPath())...) + + helpers.SetFlagImporting(ctx, true, resp.Private, &resp.Diagnostics) +} + +// End of section. //template:end import diff --git a/internal/provider/resource_iosxe_bgp_template_peer_policy_test.go b/internal/provider/resource_iosxe_bgp_template_peer_policy_test.go new file mode 100644 index 000000000..2f1fa4fd0 --- /dev/null +++ b/internal/provider/resource_iosxe_bgp_template_peer_policy_test.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 + +// Section below is generated&owned by "gen/generator.go". //template:begin imports +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +// End of section. //template:end imports + +// Section below is generated&owned by "gen/generator.go". //template:begin testAcc + +func TestAccIosxeBGPTemplatePeerPolicy(t *testing.T) { + var checks []resource.TestCheckFunc + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "name", "PEERPOLICY_1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "route_reflector_client", "true")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "send_community", "both")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "route_maps.0.in_out", "in")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "route_maps.0.route_map_name", "ROUTEMAP_1")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "allowas_in_as_number", "2")) + checks = append(checks, resource.TestCheckResourceAttr("iosxe_bgp_template_peer_policy.test", "as_override_split_horizon", "true")) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccIosxeBGPTemplatePeerPolicyPrerequisitesConfig + testAccIosxeBGPTemplatePeerPolicyConfig_minimum(), + }, + { + Config: testAccIosxeBGPTemplatePeerPolicyPrerequisitesConfig + testAccIosxeBGPTemplatePeerPolicyConfig_all(), + Check: resource.ComposeTestCheckFunc(checks...), + }, + { + ResourceName: "iosxe_bgp_template_peer_policy.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: iosxeBGPTemplatePeerPolicyImportStateIdFunc("iosxe_bgp_template_peer_policy.test"), + ImportStateVerifyIgnore: []string{}, + Check: resource.ComposeTestCheckFunc(checks...), + }, + }, + }) +} + +// End of section. //template:end testAcc + +// Section below is generated&owned by "gen/generator.go". //template:begin importStateIdFunc + +func iosxeBGPTemplatePeerPolicyImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + primary := s.RootModule().Resources[resourceName].Primary + Asn := primary.Attributes["asn"] + Name := primary.Attributes["name"] + + return fmt.Sprintf("%s,%s", Asn, Name), nil + } +} + +// End of section. //template:end importStateIdFunc + +// Section below is generated&owned by "gen/generator.go". //template:begin testPrerequisites +const testAccIosxeBGPTemplatePeerPolicyPrerequisitesConfig = ` +resource "iosxe_restconf" "PreReq0" { + path = "Cisco-IOS-XE-native:native/router/Cisco-IOS-XE-bgp:bgp=65000" + attributes = { + "id" = "65000" + } +} + +resource "iosxe_restconf" "PreReq1" { + path = "Cisco-IOS-XE-native:native/route-map=ROUTEMAP_1" + attributes = { + "name" = "ROUTEMAP_1" + } +} + +` + +// End of section. //template:end testPrerequisites + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigMinimal + +func testAccIosxeBGPTemplatePeerPolicyConfig_minimum() string { + config := `resource "iosxe_bgp_template_peer_policy" "test" {` + "\n" + config += ` asn = "65000"` + "\n" + config += ` name = "PEERPOLICY_1"` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, iosxe_restconf.PreReq1, ]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigMinimal + +// Section below is generated&owned by "gen/generator.go". //template:begin testAccConfigAll + +func testAccIosxeBGPTemplatePeerPolicyConfig_all() string { + config := `resource "iosxe_bgp_template_peer_policy" "test" {` + "\n" + config += ` asn = "65000"` + "\n" + config += ` name = "PEERPOLICY_1"` + "\n" + config += ` route_reflector_client = true` + "\n" + config += ` send_community = "both"` + "\n" + config += ` route_maps = [{` + "\n" + config += ` in_out = "in"` + "\n" + config += ` route_map_name = "ROUTEMAP_1"` + "\n" + config += ` }]` + "\n" + config += ` allowas_in_as_number = 2` + "\n" + config += ` as_override_split_horizon = true` + "\n" + config += ` depends_on = [iosxe_restconf.PreReq0, iosxe_restconf.PreReq1, ]` + "\n" + config += `}` + "\n" + return config +} + +// End of section. //template:end testAccConfigAll diff --git a/templates/guides/changelog.md.tmpl b/templates/guides/changelog.md.tmpl index 7fa3bd4df..517dfc963 100644 --- a/templates/guides/changelog.md.tmpl +++ b/templates/guides/changelog.md.tmpl @@ -16,6 +16,7 @@ description: |- - 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 `iosxe_bgp_template_peer_policy` resource ## 0.9.3 @@ -30,7 +31,7 @@ description: |- - 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 @@ -75,7 +76,7 @@ description: |- - 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