From 80bb2834813e2cb53ed3f897a32d63cb2556bbff Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Fri, 12 May 2023 17:14:57 +0200 Subject: [PATCH 01/41] r/interface_logical: reformat delete func definition --- .../providerfwk/resource_interface_logical.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/providerfwk/resource_interface_logical.go b/internal/providerfwk/resource_interface_logical.go index 8065e797..5bb9132c 100644 --- a/internal/providerfwk/resource_interface_logical.go +++ b/internal/providerfwk/resource_interface_logical.go @@ -3299,7 +3299,9 @@ func (rscData *interfaceLogicalData) readSecurityZoneInboundTraffic( return nil } -func (rscData *interfaceLogicalData) del(ctx context.Context, junSess *junos.Session) error { +func (rscData *interfaceLogicalData) del( + ctx context.Context, junSess *junos.Session, +) error { configSet := []string{ "delete interfaces " + rscData.Name.ValueString(), } @@ -3328,7 +3330,9 @@ func (rscData *interfaceLogicalData) del(ctx context.Context, junSess *junos.Ses return nil } -func (rscData *interfaceLogicalData) delOpts(_ context.Context, junSess *junos.Session) error { +func (rscData *interfaceLogicalData) delOpts( + _ context.Context, junSess *junos.Session, +) error { delPrefix := "delete interfaces " + rscData.Name.ValueString() + " " configSet := []string{ delPrefix + "description", @@ -3342,7 +3346,9 @@ func (rscData *interfaceLogicalData) delOpts(_ context.Context, junSess *junos.S return junSess.ConfigSet(configSet) } -func (rscData *interfaceLogicalData) delSecurityZone(_ context.Context, junSess *junos.Session) error { +func (rscData *interfaceLogicalData) delSecurityZone( + _ context.Context, junSess *junos.Session, +) error { configSet := []string{ "delete security zones security-zone " + rscData.SecurityZone.ValueString() + " interfaces " + rscData.Name.ValueString(), @@ -3351,7 +3357,9 @@ func (rscData *interfaceLogicalData) delSecurityZone(_ context.Context, junSess return junSess.ConfigSet(configSet) } -func (rscData *interfaceLogicalData) delRoutingInstance(_ context.Context, junSess *junos.Session) error { +func (rscData *interfaceLogicalData) delRoutingInstance( + _ context.Context, junSess *junos.Session, +) error { configSet := []string{ junos.DelRoutingInstances + rscData.RoutingInstance.ValueString() + " interface " + rscData.Name.ValueString(), From 7aa467ace093a7cf0e25a93fd5d1c093026ef681 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 15 May 2023 08:53:22 +0200 Subject: [PATCH 02/41] r/forwardingoptions_sampling_instance: fix double space in set lines when delete instance config --- .../providerfwk/resource_forwardingoptions_sampling_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providerfwk/resource_forwardingoptions_sampling_instance.go b/internal/providerfwk/resource_forwardingoptions_sampling_instance.go index d481e4ef..5fa1cfde 100644 --- a/internal/providerfwk/resource_forwardingoptions_sampling_instance.go +++ b/internal/providerfwk/resource_forwardingoptions_sampling_instance.go @@ -2144,7 +2144,7 @@ func (rscData *forwardingoptionsSamplingInstanceData) del( configSet[0] = junos.DelRoutingInstances + v + " forwarding-options sampling instance \"" + rscData.Name.ValueString() + "\"" } else { - configSet[0] = "delete " + + configSet[0] = junos.DeleteW + " forwarding-options sampling instance \"" + rscData.Name.ValueString() + "\"" } From 33b676527bb21509300fefa3b381c7eb1fe18d6c Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 15 May 2023 09:04:48 +0200 Subject: [PATCH 03/41] r/bgp_*: use new provider via framework --- .changes/bgp-with-fwk.md | 19 + docs/resources/bgp_group.md | 17 +- docs/resources/bgp_neighbor.md | 19 +- internal/providerfwk/default_resource_op.go | 21 + internal/providerfwk/provider.go | 2 + internal/providerfwk/resource_bgp_group.go | 1965 ++++++++++++++++ .../resource_bgp_group_test.go | 66 +- internal/providerfwk/resource_bgp_neighbor.go | 2020 +++++++++++++++++ .../resource_bgp_neighbor_test.go | 66 +- internal/providerfwk/resourcedata_bgp.go | 375 +++ .../providerfwk/upgradestate_bgp_group.go | 462 ++++ .../upgradestate_bgp_group_test.go | 145 ++ .../providerfwk/upgradestate_bgp_neighbor.go | 461 ++++ .../upgradestate_bgp_neighbor_test.go | 150 ++ internal/providersdk/func_resource_bgp.go | 735 ------ internal/providersdk/provider.go | 2 - internal/providersdk/resource_bgp_group.go | 1120 --------- internal/providersdk/resource_bgp_neighbor.go | 1137 ---------- 18 files changed, 5683 insertions(+), 3099 deletions(-) create mode 100644 .changes/bgp-with-fwk.md create mode 100644 internal/providerfwk/resource_bgp_group.go rename internal/{providersdk => providerfwk}/resource_bgp_group_test.go (82%) create mode 100644 internal/providerfwk/resource_bgp_neighbor.go rename internal/{providersdk => providerfwk}/resource_bgp_neighbor_test.go (84%) create mode 100644 internal/providerfwk/resourcedata_bgp.go create mode 100644 internal/providerfwk/upgradestate_bgp_group.go create mode 100644 internal/providerfwk/upgradestate_bgp_group_test.go create mode 100644 internal/providerfwk/upgradestate_bgp_neighbor.go create mode 100644 internal/providerfwk/upgradestate_bgp_neighbor_test.go delete mode 100644 internal/providersdk/func_resource_bgp.go delete mode 100644 internal/providersdk/resource_bgp_group.go delete mode 100644 internal/providersdk/resource_bgp_neighbor.go diff --git a/.changes/bgp-with-fwk.md b/.changes/bgp-with-fwk.md new file mode 100644 index 00000000..756b4289 --- /dev/null +++ b/.changes/bgp-with-fwk.md @@ -0,0 +1,19 @@ + +ENHANCEMENTS: + +* **resource/junos_bgp_group**: + * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + some of config errors are now sent during Plan instead of during Apply + optional boolean attributes doesn't accept value *false* + optional string attributes doesn't accept *empty* value + the resource schema has been upgraded to have one-blocks in single mode instead of list + * `advertise_external` is now computed to `true` when `advertise_external_conditional` is `true` (instead of the 2 attributes conflicting) + * `bfd_liveness_detection.version` now generate an error if the value is not in one of strings `0`, `1` or `automatic` +* **resource/junos_bgp_neighbor**: + * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + some of config errors are now sent during Plan instead of during Apply + optional boolean attributes doesn't accept value *false* + optional string attributes doesn't accept *empty* value + the resource schema has been upgraded to have one-blocks in single mode instead of list + * `advertise_external` is now computed to `true` when `advertise_external_conditional` is `true` (instead of the 2 attributes conflicting) + * `bfd_liveness_detection.version` now generate an error if the value is not in one of strings `0`, `1` or `automatic` diff --git a/docs/resources/bgp_group.md b/docs/resources/bgp_group.md index c12cf0d0..aa3a3060 100644 --- a/docs/resources/bgp_group.md +++ b/docs/resources/bgp_group.md @@ -25,7 +25,7 @@ The following arguments are supported: - **name** (Required, String, Forces new resource) Name of group. - **routing_instance** (Optional, String, Forces new resource) - Routing instance for bgp protocol. + Routing instance for bgp protocol if not root level. Need to be `default` or name of routing instance. Defaults to `default`. - **type** (Optional, String, Forces new resource) @@ -34,12 +34,11 @@ The following arguments are supported: Defaults to `external`. - **accept_remote_nexthop** (Optional, Boolean) Allow import policy to specify a non-directly connected next-hop. -- **advertise_external** (Optional, Boolean) +- **advertise_external** (Optional, Computed, Boolean) Advertise best external routes. - Conflict with `advertise_external_conditional`. + Computed to set to `true` when `advertise_external_conditional` is true. - **advertise_external_conditional** (Optional, Boolean) - Route matches active route upto med-comparison rule. - Conflict with `advertise_external`. + Route matches active route upto med-comparison rule. - **advertise_inactive** (Optional, Boolean) Advertise inactive routes. - **advertise_peer_as** (Optional, Boolean) @@ -71,7 +70,8 @@ The following arguments are supported: - **multiple_as** (Optional, Boolean) Use paths received from different ASs. - **cluster** (Optional, String) - Cluster identifier. Must be a valid IP address. + Cluster identifier. + Must be a valid IP address. - **damping** (Optional, Boolean) Enable route flap damping. - **export** (Optional, List of String) @@ -186,7 +186,8 @@ The following arguments are supported: - **transmit_interval_threshold** (Optional, Number) High transmit interval triggering a trap (milliseconds). - **version** (Optional, String) - BFD protocol version number. + BFD protocol version number. + Need to be `0`, `1` or `automatic`. --- @@ -198,7 +199,7 @@ Also for `family_inet6` and `family_evpn` (except `nlri_type`) NLRI type. Need to be `any`, `flow`, `labeled-unicast`, `unicast` or `multicast`. - **accepted_prefix_limit** (Optional, Block) - Define maximum number of prefixes accepted from a peer and options. + Define maximum number of prefixes accepted from a peer. - **maximum** (Required, Number) Maximum number of prefixes accepted from a peer (1..4294967295). - **teardown** (Optional, Number) diff --git a/docs/resources/bgp_neighbor.md b/docs/resources/bgp_neighbor.md index c699a1c2..ae947db2 100644 --- a/docs/resources/bgp_neighbor.md +++ b/docs/resources/bgp_neighbor.md @@ -26,19 +26,18 @@ The following arguments are supported: - **ip** (Required, String, Forces new resource) IP of neighbor. - **routing_instance** (Optional, String, Forces new resource) - Routing instance for bgp protocol. + Routing instance for bgp protocol if not root level. Need to be `default` or name of routing instance. Defaults to `default`. - **group** (Required, String, Forces new resource) - Name of BGP group for this neighbor + Name of BGP group for this neighbor. - **accept_remote_nexthop** (Optional, Boolean) Allow import policy to specify a non-directly connected next-hop. -- **advertise_external** (Optional, Boolean) +- **advertise_external** (Optional, Computed, Boolean) Advertise best external routes. - Conflict with `advertise_external_conditional`. + Computed to set to `true` when `advertise_external_conditional` is true. - **advertise_external_conditional** (Optional, Boolean) - Route matches active route upto med-comparison rule. - Conflict with `advertise_external`. + Route matches active route upto med-comparison rule. - **advertise_inactive** (Optional, Boolean) Advertise inactive routes. - **advertise_peer_as** (Optional, Boolean) @@ -70,7 +69,8 @@ The following arguments are supported: - **multiple_as** (Optional, Boolean) Use paths received from different ASs. - **cluster** (Optional, String) - Cluster identifier. Must be a valid IP address. + Cluster identifier. + Must be a valid IP address. - **damping** (Optional, Boolean) Enable route flap damping. - **export** (Optional, List of String) @@ -185,7 +185,8 @@ The following arguments are supported: - **transmit_interval_threshold** (Optional, Number) High transmit interval triggering a trap (milliseconds). - **version** (Optional, String) - BFD protocol version number. + BFD protocol version number. + Need to be `0`, `1` or `automatic`. --- @@ -197,7 +198,7 @@ Also for `family_inet6` and `family_evpn` (except `nlri_type`) NLRI type. Need to be `any`, `flow`, `labeled-unicast`, `unicast` or `multicast`. - **accepted_prefix_limit** (Optional, Block) - Define maximum number of prefixes accepted from a peer and options. + Define maximum number of prefixes accepted from a peer. - **maximum** (Required, Number) Maximum number of prefixes accepted from a peer (1..4294967295). - **teardown** (Optional, Number) diff --git a/internal/providerfwk/default_resource_op.go b/internal/providerfwk/default_resource_op.go index ae01998d..00e79f94 100644 --- a/internal/providerfwk/default_resource_op.go +++ b/internal/providerfwk/default_resource_op.go @@ -31,6 +31,11 @@ type resourceDataReadFrom2String interface { read(context.Context, string, string, *junos.Session) error } +type resourceDataReadFrom3String interface { + resourceDataNullID + read(context.Context, string, string, string, *junos.Session) error +} + type resourceDataReadFrom4String interface { resourceDataNullID read(context.Context, string, string, string, string, *junos.Session) error @@ -161,6 +166,9 @@ func defaultResourceRead( if data2, ok := data.(resourceDataReadFrom2String); ok { err = data2.read(ctx, mainAttrValues[0], mainAttrValues[1], junSess) } + if data3, ok := data.(resourceDataReadFrom3String); ok { + err = data3.read(ctx, mainAttrValues[0], mainAttrValues[1], mainAttrValues[2], junSess) + } if data4, ok := data.(resourceDataReadFrom4String); ok { err = data4.read(ctx, mainAttrValues[0], mainAttrValues[1], mainAttrValues[2], mainAttrValues[3], junSess) } @@ -354,6 +362,19 @@ func defaultResourceImportState( err = data2.read(ctx, idList[0], idList[1], junSess) } + if data3, ok := data.(resourceDataReadFrom3String); ok { + idList := strings.Split(req.ID, junos.IDSeparator) + if len(idList) < 3 { + resp.Diagnostics.AddError( + "Bad ID Format", + fmt.Sprintf("missing element(s) in id with separator %q", junos.IDSeparator), + ) + + return + } + + err = data3.read(ctx, idList[0], idList[1], idList[2], junSess) + } if data4, ok := data.(resourceDataReadFrom4String); ok { idList := strings.Split(req.ID, junos.IDSeparator) if len(idList) < 4 { diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index fca99747..504ad314 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -197,6 +197,8 @@ func (p *junosProvider) DataSources(_ context.Context) []func() datasource.DataS func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ + newBgpGroupResource, + newBgpNeighborResource, newForwardingoptionsSamplingResource, newForwardingoptionsSamplingInstanceResource, newInterfaceLogicalResource, diff --git a/internal/providerfwk/resource_bgp_group.go b/internal/providerfwk/resource_bgp_group.go new file mode 100644 index 00000000..557dc011 --- /dev/null +++ b/internal/providerfwk/resource_bgp_group.go @@ -0,0 +1,1965 @@ +package providerfwk + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfplanmodifier" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + "github.com/jeremmfr/terraform-provider-junos/internal/utils" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &bgpGroup{} + _ resource.ResourceWithConfigure = &bgpGroup{} + _ resource.ResourceWithModifyPlan = &bgpGroup{} + _ resource.ResourceWithValidateConfig = &bgpGroup{} + _ resource.ResourceWithImportState = &bgpGroup{} + _ resource.ResourceWithUpgradeState = &bgpGroup{} +) + +type bgpGroup struct { + client *junos.Client +} + +func newBgpGroupResource() resource.Resource { + return &bgpGroup{} +} + +func (rsc *bgpGroup) typeName() string { + return providerName + "_bgp_group" +} + +func (rsc *bgpGroup) junosName() string { + return "bgp group" +} + +func (rsc *bgpGroup) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *bgpGroup) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *bgpGroup) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *bgpGroup) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Version: 1, + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format `_-_`.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name of group.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(junos.DefaultW), + Description: "Routing instance for bgp protocol if not root level.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "type": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("external"), + Description: "Type of peer group.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf("internal", "external"), + }, + }, + "accept_remote_nexthop": schema.BoolAttribute{ + Optional: true, + Description: "Allow import policy to specify a non-directly connected next-hop.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_external": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Advertise best external routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_external_conditional": schema.BoolAttribute{ + Optional: true, + Description: "Route matches active route upto med-comparison rule.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_inactive": schema.BoolAttribute{ + Optional: true, + Description: "Advertise inactive routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_peer_as": schema.BoolAttribute{ + Optional: true, + Description: "Advertise routes received from the same autonomous system.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_advertise_peer_as": schema.BoolAttribute{ + Optional: true, + Description: "Don't advertise routes received from the same autonomous system.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_override": schema.BoolAttribute{ + Optional: true, + Description: "Replace neighbor AS number with our AS number.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + Description: "Authentication algorithm name.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "authentication_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + Description: "MD5 authentication key.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 126), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + Description: "Key chain name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "cluster": schema.StringAttribute{ + Optional: true, + Description: "Cluster identifier.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "damping": schema.BoolAttribute{ + Optional: true, + Description: "Enable route flap damping.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "export": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Export policy list.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "hold_time": schema.Int64Attribute{ + Optional: true, + Description: "Hold time used when negotiating with a peer.", + Validators: []validator.Int64{ + int64validator.Between(3, 65535), + }, + }, + "import": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Import policy list.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "keep_all": schema.BoolAttribute{ + Optional: true, + Description: "Retain all routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "keep_none": schema.BoolAttribute{ + Optional: true, + Description: "Retain no routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_address": schema.StringAttribute{ + Optional: true, + Description: "Address of local end of BGP session.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "local_as": schema.StringAttribute{ + Optional: true, + Description: "Local autonomous system number.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "local_as_alias": schema.BoolAttribute{ + Optional: true, + Description: "Treat this AS as an alias to the system AS.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_as_loops": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of times this AS can be in an AS path (1..10).", + Validators: []validator.Int64{ + int64validator.Between(1, 10), + }, + }, + "local_as_no_prepend_global_as": schema.BoolAttribute{ + Optional: true, + Description: "Do not prepend global autonomous-system number in advertised paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_as_private": schema.BoolAttribute{ + Optional: true, + Description: "Hide this local AS in paths learned from this peering.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_interface": schema.StringAttribute{ + Optional: true, + Description: "Local interface for IPv6 link local EBGP peering.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.String1DotCount(), + }, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + Description: "Value of LOCAL_PREF path attribute.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "log_updown": schema.BoolAttribute{ + Optional: true, + Description: "Log a message for peer state transitions.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out": schema.Int64Attribute{ + Optional: true, + Description: "Route metric sent in MED.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "metric_out_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Track the IGP metric.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_igp_delay_med_update": schema.BoolAttribute{ + Optional: true, + Description: "Delay updating MED when IGP metric increases.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_igp_offset": schema.Int64Attribute{ + Optional: true, + Description: "Metric offset for MED.", + Validators: []validator.Int64{ + int64validator.Between(-2147483648, 2147483647), + }, + }, + "metric_out_minimum_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Track the minimum IGP metric.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_minimum_igp_offset": schema.Int64Attribute{ + Optional: true, + Description: "Metric offset for MED.", + Validators: []validator.Int64{ + int64validator.Between(-2147483648, 2147483647), + }, + }, + "mtu_discovery": schema.BoolAttribute{ + Optional: true, + Description: "Enable TCP path MTU discovery.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "multihop": schema.BoolAttribute{ + Optional: true, + Description: "Configure an EBGP multihop session.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "out_delay": schema.Int64Attribute{ + Optional: true, + Description: "How long before exporting routes from routing table.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "passive": schema.BoolAttribute{ + Optional: true, + Description: "Do not send open messages to a peer.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "peer_as": schema.StringAttribute{ + Optional: true, + Description: "Autonomous system number.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "remove_private": schema.BoolAttribute{ + Optional: true, + Description: "Remove well-known private AS numbers.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "bfd_liveness_detection": schema.SingleNestedBlock{ + Description: "Define Bidirectional Forwarding Detection (BFD) options.", + Attributes: map[string]schema.Attribute{ + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + Description: "Authentication algorithm name.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + Description: "Authentication key chain name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "authentication_loose_check": schema.BoolAttribute{ + Optional: true, + Description: "Verify authentication only if authentication is negotiated.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "detection_time_threshold": schema.Int64Attribute{ + Optional: true, + Description: "High detection-time triggering a trap (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "holddown_interval": schema.Int64Attribute{ + Optional: true, + Description: "Time to hold the session-UP notification to the client (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "minimum_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum transmit and receive interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "minimum_receive_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum receive interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "multiplier": schema.Int64Attribute{ + Optional: true, + Description: "Detection time multiplier (1..255).", + Validators: []validator.Int64{ + int64validator.Between(1, 255), + }, + }, + "session_mode": schema.StringAttribute{ + Optional: true, + Description: "BFD single-hop or multihop session-mode.", + Validators: []validator.String{ + stringvalidator.OneOf("automatic", "multihop", "single-hop"), + }, + }, + "transmit_interval_minimum_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum transmit interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "transmit_interval_threshold": schema.Int64Attribute{ + Optional: true, + Description: "High transmit interval triggering a trap (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "version": schema.StringAttribute{ + Optional: true, + Description: "BFD protocol version number.", + Validators: []validator.String{ + stringvalidator.OneOf("0", "1", "automatic"), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "bgp_multipath": schema.SingleNestedBlock{ + Description: "Allow load sharing among multiple BGP paths.", + Attributes: map[string]schema.Attribute{ + "allow_protection": schema.BoolAttribute{ + Optional: true, + Description: "Allows the BGP multipath and protection to co-exist.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "disable": schema.BoolAttribute{ + Optional: true, + Description: "Disable Multipath.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "multiple_as": schema.BoolAttribute{ + Optional: true, + Description: "Use paths received from different ASs.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "family_evpn": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure EVPN NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("signaling"), + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("signaling"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "family_inet": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure IPv4 NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("any", "flow", "labeled-unicast", "unicast", "multicast"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "family_inet6": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure IPv6 NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("any", "flow", "labeled-unicast", "unicast", "multicast"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "graceful_restart": schema.SingleNestedBlock{ + Description: "Define BGP graceful restart options.", + Attributes: map[string]schema.Attribute{ + "disable": schema.BoolAttribute{ + Optional: true, + Description: "Disable graceful restart.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "restart_time": schema.Int64Attribute{ + Optional: true, + Description: "Restart time used when negotiating with a peer (1..600).", + Validators: []validator.Int64{ + int64validator.Between(1, 600), + }, + }, + "stale_route_time": schema.Int64Attribute{ + Optional: true, + Description: "Maximum time for which stale routes are kept (1..600).", + Validators: []validator.Int64{ + int64validator.Between(1, 600), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + } +} + +func (rsc *bgpGroup) schemaFamilyPrefixLimitAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "maximum": schema.Int64Attribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Maximum number of prefixes accepted from a peer.", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "teardown": schema.Int64Attribute{ + Optional: true, + Description: "Clear peer connection on reaching limit with this percentage of prefix-limit to start warnings.", + Validators: []validator.Int64{ + int64validator.Between(1, 100), + }, + }, + "teardown_idle_timeout": schema.Int64Attribute{ + Optional: true, + Description: "Timeout before attempting to restart peer.", + Validators: []validator.Int64{ + int64validator.Between(1, 2400), + }, + }, + "teardown_idle_timeout_forever": schema.BoolAttribute{ + Optional: true, + Description: "Idle the peer until the user intervenes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + } +} + +type bgpGroupData struct { + AcceptRemoteNexthop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemovePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export []types.String `tfsdk:"export"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import []types.String `tfsdk:"import"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + Name types.String `tfsdk:"name"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + Type types.String `tfsdk:"type"` + BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipath *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn []bgpBlockFamily `tfsdk:"family_evpn"` + FamilyInet []bgpBlockFamily `tfsdk:"family_inet"` + FamilyInet6 []bgpBlockFamily `tfsdk:"family_inet6"` + GracefulRestart *bgpBlockGracefulRestart `tfsdk:"graceful_restart"` +} + +type bgpGroupConfig struct { + AcceptRemoteDesktop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemotePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export types.List `tfsdk:"export"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import types.List `tfsdk:"import"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + Name types.String `tfsdk:"name"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + Type types.String `tfsdk:"type"` + BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipah *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn types.List `tfsdk:"family_evpn"` + FamilyInet types.List `tfsdk:"family_inet"` + FamilyInet6 types.List `tfsdk:"family_inet6"` + GracefulRestart *bgpBlockGracefulRestart `tfsdk:"graceful_restart"` +} + +func (rsc *bgpGroup) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config bgpGroupConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.AdvertisePeerAS.IsNull() && + !config.NoAdvertisePeerAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("advertise_peer_as"), + tfdiag.ConflictConfigErrSummary, + "advertise_peer_as and no_advertise_peer_as can't be true in same time ", + ) + } + if !config.KeepAll.IsNull() && + !config.KeepNone.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("keep_all"), + tfdiag.ConflictConfigErrSummary, + "keep_all and keep_none can't be true in same time ", + ) + } + if !config.AuthenticationKey.IsNull() { + if !config.AuthenticationAlgorithm.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("authentication_algorithm"), + tfdiag.ConflictConfigErrSummary, + "authentication_algorithm and authentication_key cannot be configured together", + ) + } + if !config.AuthenticationKeyChain.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("authentication_key_chain"), + tfdiag.ConflictConfigErrSummary, + "authentication_key_chain and authentication_key cannot be configured together", + ) + } + } + if !config.LocalASAlias.IsNull() { + if !config.LocalASPrivate.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_alias"), + tfdiag.ConflictConfigErrSummary, + "local_as_alias and local_as_private cannot be configured together", + ) + } + if !config.LocalASNoPrependGlobalAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_alias"), + tfdiag.ConflictConfigErrSummary, + "local_as_alias and local_as_no_prepend_global_as cannot be configured together", + ) + } + } + if !config.LocalASPrivate.IsNull() { + if !config.LocalASNoPrependGlobalAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_private"), + tfdiag.ConflictConfigErrSummary, + "local_as_private and local_as_no_prepend_global_as cannot be configured together", + ) + } + } + if !config.MetricOut.IsNull() { + if !config.MetricOutIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp cannot be configured together", + ) + } + if !config.MetricOutIgpDelayMedUpdate.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp_delay_med_update"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp_delay_med_update cannot be configured together", + ) + } + if !config.MetricOutIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp_offset cannot be configured together", + ) + } + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgp.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgpDelayMedUpdate.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_delay_med_update and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_delay_med_update and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgpOffset.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_offset and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_offset and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.FamilyEvpn.IsNull() && !config.FamilyEvpn.IsUnknown() { + var configFamilyEvpn []bgpBlockFamily + asDiags := config.FamilyEvpn.ElementsAs(ctx, &configFamilyEvpn, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyEvpnNlriType := make(map[string]struct{}) + for i, block := range configFamilyEvpn { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyEvpnNlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_evpn blocks with the same nlri_type %q", nlriType), + ) + } else { + familyEvpnNlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_evpn block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_evpn block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_evpn block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_evpn block ", + ) + } + } + } + } + if !config.FamilyInet.IsNull() && !config.FamilyInet.IsUnknown() { + var configFamilyInet []bgpBlockFamily + asDiags := config.FamilyInet.ElementsAs(ctx, &configFamilyInet, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyInetNlriType := make(map[string]struct{}) + for i, block := range configFamilyInet { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyInetNlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_inet blocks with the same nlri_type %q", nlriType), + ) + } else { + familyInetNlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_inet block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_inet block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_inet block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_inet block ", + ) + } + } + } + } + if !config.FamilyInet6.IsNull() && !config.FamilyInet6.IsUnknown() { + var configFamilyInet6 []bgpBlockFamily + asDiags := config.FamilyInet6.ElementsAs(ctx, &configFamilyInet6, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyInet6NlriType := make(map[string]struct{}) + for i, block := range configFamilyInet6 { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyInet6NlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_inet6 blocks with the same nlri_type %q", nlriType), + ) + } else { + familyInet6NlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_inet6 block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_inet6 block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_inet6 block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_inet6 block ", + ) + } + } + } + } + if config.GracefulRestart != nil { + if !config.GracefulRestart.Disable.IsNull() { + if !config.GracefulRestart.RestartTime.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("graceful_restart").AtName("restart_time"), + tfdiag.ConflictConfigErrSummary, + "restart_time and disable cannot be configured together"+ + " in graceful_restart block", + ) + } + if !config.GracefulRestart.StaleRouteTime.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("graceful_restart").AtName("stale_route_time"), + tfdiag.ConflictConfigErrSummary, + "stale_route_time and disable cannot be configured together"+ + " in graceful_restart block", + ) + } + } + } +} + +func (rsc *bgpGroup) ModifyPlan( + ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse, +) { + if req.Plan.Raw.IsNull() { + return + } + + var config, plan bgpGroupConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if config.AdvertiseExternal.IsNull() { + if config.AdvertiseExternalConditional.IsNull() { + plan.AdvertiseExternal = types.BoolNull() + } else if !plan.AdvertiseExternalConditional.IsNull() && + !plan.AdvertiseExternalConditional.IsUnknown() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if config.MetricOutIgp.IsNull() { + if config.MetricOutIgpDelayMedUpdate.IsNull() && + config.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolNull() + } else { + if !plan.MetricOutIgpDelayMedUpdate.IsNull() && + !plan.MetricOutIgpDelayMedUpdate.IsUnknown() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() && + !plan.MetricOutIgpOffset.IsUnknown() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + } + if config.MetricOutMinimumIgp.IsNull() { + if config.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolNull() + } else if !plan.MetricOutMinimumIgpOffset.IsNull() && + !plan.MetricOutMinimumIgpOffset.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) +} + +func (rsc *bgpGroup) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan bgpGroupData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + if plan.AdvertiseExternal.IsUnknown() { + plan.AdvertiseExternal = types.BoolNull() + if plan.AdvertiseExternalConditional.ValueBool() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if plan.MetricOutIgp.IsUnknown() { + plan.MetricOutIgp = types.BoolNull() + if plan.MetricOutIgpDelayMedUpdate.ValueBool() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + if plan.MetricOutMinimumIgp.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolNull() + if !plan.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + instanceExists, err := checkRoutingInstanceExists(fnCtx, v, junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !instanceExists { + resp.Diagnostics.AddAttributeError( + path.Root("routing_instance"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("routing instance %q doesn't exist", v), + ) + + return false + } + } + bgpGroupExists, err := checkBgpGroupExists( + fnCtx, + plan.Name.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if bgpGroupExists { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists in routing-instance %q", plan.Name.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + } + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + bgpGroupExists, err := checkBgpGroupExists( + fnCtx, + plan.Name.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !bgpGroupExists { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists in routing-instance %q after commit "+ + "=> check your config", plan.Name.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + } + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *bgpGroup) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data bgpGroupData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom2String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + state.RoutingInstance.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *bgpGroup) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state bgpGroupData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if plan.AdvertiseExternal.IsUnknown() { + plan.AdvertiseExternal = types.BoolNull() + if plan.AdvertiseExternalConditional.ValueBool() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if plan.MetricOutIgp.IsUnknown() { + plan.MetricOutIgp = types.BoolNull() + if plan.MetricOutIgpDelayMedUpdate.ValueBool() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + if plan.MetricOutMinimumIgp.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolNull() + if !plan.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + var _ resourceDataDelWithOpts = &state + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *bgpGroup) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state bgpGroupData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *bgpGroup) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data bgpGroupData + + var _ resourceDataReadFrom2String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be "+junos.IDSeparator+")", req.ID), + ) +} + +func checkBgpGroupExists( + _ context.Context, + name, + routingInstance string, + junSess *junos.Session, +) ( + _ bool, err error, +) { + var showConfig string + if routingInstance == "" || routingInstance == junos.DefaultW { + showConfig, err = junSess.Command(junos.CmdShowConfig + + "protocols bgp group \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + } else { + showConfig, err = junSess.Command(junos.CmdShowConfig + + junos.RoutingInstancesWS + routingInstance + " " + + "protocols bgp group \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *bgpGroupData) fillID() { + if v := rscData.RoutingInstance.ValueString(); v != "" { + rscData.ID = types.StringValue(rscData.Name.ValueString() + junos.IDSeparator + v) + } else { + rscData.ID = types.StringValue(rscData.Name.ValueString() + junos.IDSeparator + junos.DefaultW) + } +} + +func (rscData *bgpGroupData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *bgpGroupData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := "set protocols bgp group \"" + rscData.Name.ValueString() + "\" " + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + setPrefix = junos.SetRoutingInstances + v + + " protocols bgp group \"" + rscData.Name.ValueString() + "\" " + } + configSet := []string{ + setPrefix + "type " + rscData.Type.ValueString(), + } + + if rscData.AcceptRemoteNexthop.ValueBool() { + configSet = append(configSet, setPrefix+"accept-remote-nexthop") + } + if rscData.AdvertiseExternal.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-external") + } + if rscData.AdvertiseExternalConditional.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-external conditional") + } + if rscData.AdvertiseInactive.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-inactive") + } + if rscData.AdvertisePeerAS.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-peer-as") + } + if rscData.NoAdvertisePeerAS.ValueBool() { + configSet = append(configSet, setPrefix+"no-advertise-peer-as") + } + if rscData.ASOverride.ValueBool() { + configSet = append(configSet, setPrefix+"as-override") + } + if v := rscData.AuthenticationAlgorithm.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-algorithm "+v) + } + if v := rscData.AuthenticationKey.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-key \""+v+"\"") + } + if v := rscData.AuthenticationKeyChain.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-key-chain \""+v+"\"") + } + if v := rscData.Cluster.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"cluster "+v) + } + if rscData.Damping.ValueBool() { + configSet = append(configSet, setPrefix+"damping") + } + for _, v := range rscData.Export { + configSet = append(configSet, setPrefix+"export "+v.ValueString()) + } + if !rscData.HoldTime.IsNull() { + configSet = append(configSet, setPrefix+"hold-time "+ + utils.ConvI64toa(rscData.HoldTime.ValueInt64())) + } + for _, v := range rscData.Import { + configSet = append(configSet, setPrefix+"import "+v.ValueString()) + } + if rscData.KeepAll.ValueBool() { + configSet = append(configSet, setPrefix+"keep all") + } + if rscData.KeepNone.ValueBool() { + configSet = append(configSet, setPrefix+"keep none") + } + if v := rscData.LocalAddress.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-address "+v) + } + if v := rscData.LocalAS.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-as "+v) + } + if rscData.LocalASAlias.ValueBool() { + configSet = append(configSet, setPrefix+"local-as alias") + } + if !rscData.LocalASLoops.IsNull() { + configSet = append(configSet, setPrefix+"local-as loops "+ + utils.ConvI64toa(rscData.LocalASLoops.ValueInt64())) + } + if rscData.LocalASNoPrependGlobalAS.ValueBool() { + configSet = append(configSet, setPrefix+"local-as no-prepend-global-as") + } + if rscData.LocalASPrivate.ValueBool() { + configSet = append(configSet, setPrefix+"local-as private") + } + if v := rscData.LocalInterface.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-interface "+v) + } + if !rscData.LocalPreference.IsNull() { + configSet = append(configSet, setPrefix+"local-preference "+ + utils.ConvI64toa(rscData.LocalPreference.ValueInt64())) + } + if rscData.LogUpdown.ValueBool() { + configSet = append(configSet, setPrefix+"log-updown") + } + if !rscData.MetricOut.IsNull() { + configSet = append(configSet, setPrefix+"metric-out "+ + utils.ConvI64toa(rscData.MetricOut.ValueInt64())) + } + if rscData.MetricOutIgp.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out igp") + } + if rscData.MetricOutIgpDelayMedUpdate.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out igp delay-med-update") + } + if !rscData.MetricOutIgpOffset.IsNull() { + configSet = append(configSet, setPrefix+"metric-out igp "+ + utils.ConvI64toa(rscData.MetricOutIgpOffset.ValueInt64())) + } + if rscData.MetricOutMinimumIgp.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out minimum-igp") + } + if !rscData.MetricOutMinimumIgpOffset.IsNull() { + configSet = append(configSet, setPrefix+"metric-out minimum-igp "+ + utils.ConvI64toa(rscData.MetricOutMinimumIgpOffset.ValueInt64())) + } + if rscData.MtuDiscovery.ValueBool() { + configSet = append(configSet, setPrefix+"mtu-discovery") + } + if rscData.Multihop.ValueBool() { + configSet = append(configSet, setPrefix+"multihop") + } + if !rscData.OutDelay.IsNull() { + configSet = append(configSet, setPrefix+"out-delay "+ + utils.ConvI64toa(rscData.OutDelay.ValueInt64())) + } + if rscData.Passive.ValueBool() { + configSet = append(configSet, setPrefix+"passive") + } + if v := rscData.PeerAS.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"peer-as "+v) + } + if !rscData.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(rscData.Preference.ValueInt64())) + } + if rscData.RemovePrivate.ValueBool() { + configSet = append(configSet, setPrefix+"remove-private") + } + if rscData.BfdLivenessDetection != nil { + if rscData.BfdLivenessDetection.isEmpty() { + return path.Root("bfd_liveness_detection").AtName("*"), + fmt.Errorf("bfd_liveness_detection block is empty") + } + + configSet = append(configSet, rscData.BfdLivenessDetection.configSet(setPrefix)...) + } + if rscData.BgpMultipath != nil { + configSet = append(configSet, rscData.BgpMultipath.configSet(setPrefix)...) + } + familyEvpnNlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyEvpn { + nlriType := block.NlriType.ValueString() + if _, ok := familyEvpnNlriType[nlriType]; ok { + return path.Root("family_evpn").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_evpn blocks with the same nlri_type %q", nlriType) + } + familyEvpnNlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family evpn ", path.Root("family_evpn").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + familyInetNlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyInet { + nlriType := block.NlriType.ValueString() + if _, ok := familyInetNlriType[nlriType]; ok { + return path.Root("family_inet").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_inet blocks with the same nlri_type %q", nlriType) + } + familyInetNlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family inet ", path.Root("family_inet").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + familyInet6NlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyInet6 { + nlriType := block.NlriType.ValueString() + if _, ok := familyInet6NlriType[nlriType]; ok { + return path.Root("family_inet6").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_inet6 blocks with the same nlri_type %q", nlriType) + } + familyInet6NlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family inet6 ", path.Root("family_inet6").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + if rscData.GracefulRestart != nil { + configSet = append(configSet, rscData.GracefulRestart.configSet(setPrefix)...) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *bgpGroupData) read( + _ context.Context, name, routingInstance string, junSess *junos.Session, +) ( + err error, +) { + var showConfig string + if routingInstance == "" || routingInstance == junos.DefaultW { + showConfig, err = junSess.Command(junos.CmdShowConfig + + "protocols bgp group \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + } else { + showConfig, err = junSess.Command(junos.CmdShowConfig + + junos.RoutingInstancesWS + routingInstance + " " + + "protocols bgp group \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + if routingInstance == "" { + rscData.RoutingInstance = types.StringValue(junos.DefaultW) + } else { + rscData.RoutingInstance = types.StringValue(routingInstance) + } + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case balt.CutPrefixInString(&itemTrim, "type "): + rscData.Type = types.StringValue(itemTrim) + case itemTrim == "accept-remote-nexthop": + rscData.AcceptRemoteNexthop = types.BoolValue(true) + case itemTrim == "advertise-external": + rscData.AdvertiseExternal = types.BoolValue(true) + case itemTrim == "advertise-external conditional": + rscData.AdvertiseExternal = types.BoolValue(true) + rscData.AdvertiseExternalConditional = types.BoolValue(true) + case itemTrim == "advertise-inactive": + rscData.AdvertiseInactive = types.BoolValue(true) + case itemTrim == "advertise-peer-as": + rscData.AdvertisePeerAS = types.BoolValue(true) + case itemTrim == "no-advertise-peer-as": + rscData.NoAdvertisePeerAS = types.BoolValue(true) + case itemTrim == "as-override": + rscData.ASOverride = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "authentication-algorithm "): + rscData.AuthenticationAlgorithm = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "authentication-key "): + rscData.AuthenticationKey, err = tfdata.JunosDecode(strings.Trim(itemTrim, "\""), "authentication-key") + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "authentication-key-chain "): + rscData.AuthenticationKeyChain = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "cluster "): + rscData.Cluster = types.StringValue(itemTrim) + case itemTrim == "damping": + rscData.Damping = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "export "): + rscData.Export = append(rscData.Export, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "hold-time "): + rscData.HoldTime, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "import "): + rscData.Import = append(rscData.Import, types.StringValue(itemTrim)) + case itemTrim == "keep all": + rscData.KeepAll = types.BoolValue(true) + case itemTrim == "keep none": + rscData.KeepNone = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "local-address "): + rscData.LocalAddress = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "local-as "): + switch { + case itemTrim == "private": + rscData.LocalASPrivate = types.BoolValue(true) + case itemTrim == "alias": + rscData.LocalASAlias = types.BoolValue(true) + case itemTrim == "no-prepend-global-as": + rscData.LocalASNoPrependGlobalAS = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "loops "): + rscData.LocalASLoops, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + default: + rscData.LocalAS = types.StringValue(itemTrim) + } + case balt.CutPrefixInString(&itemTrim, "local-interface "): + rscData.LocalInterface = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "local-preference "): + rscData.LocalPreference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "log-updown": + rscData.LogUpdown = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "metric-out "): + switch { + case balt.CutPrefixInString(&itemTrim, "igp"): + rscData.MetricOutIgp = types.BoolValue(true) + switch { + case itemTrim == " delay-med-update": + rscData.MetricOutIgpDelayMedUpdate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, " "): + rscData.MetricOutIgpOffset, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case balt.CutPrefixInString(&itemTrim, "minimum-igp"): + rscData.MetricOutMinimumIgp = types.BoolValue(true) + if balt.CutPrefixInString(&itemTrim, " ") { + rscData.MetricOutMinimumIgpOffset, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + default: + rscData.MetricOut, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case itemTrim == "mtu-discovery": + rscData.MtuDiscovery = types.BoolValue(true) + case itemTrim == "multihop": + rscData.Multihop = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "out-delay "): + rscData.OutDelay, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "passive": + rscData.Passive = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "peer-as "): + rscData.PeerAS = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "preference "): + rscData.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "remove-private": + rscData.RemovePrivate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): + if rscData.BfdLivenessDetection == nil { + rscData.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{} + } + if err := rscData.BfdLivenessDetection.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "family evpn "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyEvpn bgpBlockFamily + rscData.FamilyEvpn, familyEvpn = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyEvpn, "NlriType", itemTrimFields[0], + ) + familyEvpn.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyEvpn.read(itemTrim); err != nil { + return err + } + rscData.FamilyEvpn = append(rscData.FamilyEvpn, familyEvpn) + case balt.CutPrefixInString(&itemTrim, "family inet "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyInet bgpBlockFamily + rscData.FamilyInet, familyInet = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyInet, "NlriType", itemTrimFields[0], + ) + familyInet.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyInet.read(itemTrim); err != nil { + return err + } + rscData.FamilyInet = append(rscData.FamilyInet, familyInet) + case balt.CutPrefixInString(&itemTrim, "family inet6 "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyInet6 bgpBlockFamily + rscData.FamilyInet6, familyInet6 = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyInet6, "NlriType", itemTrimFields[0], + ) + familyInet6.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyInet6.read(itemTrim); err != nil { + return err + } + rscData.FamilyInet6 = append(rscData.FamilyInet6, familyInet6) + case balt.CutPrefixInString(&itemTrim, "multipath"): + if rscData.BgpMultipath == nil { + rscData.BgpMultipath = &bgpBlockBgpMultipath{} + } + rscData.BgpMultipath.read(itemTrim) + case balt.CutPrefixInString(&itemTrim, "graceful-restart"): + if rscData.GracefulRestart == nil { + rscData.GracefulRestart = &bgpBlockGracefulRestart{} + } + if err := rscData.GracefulRestart.read(itemTrim); err != nil { + return err + } + } + } + } + + return nil +} + +func (rscData *bgpGroupData) delOpts( + _ context.Context, junSess *junos.Session, +) error { + configSet := make([]string, 0) + delPrefix := "delete protocols bgp group \"" + rscData.Name.ValueString() + "\" " + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + delPrefix = junos.DelRoutingInstances + v + + " protocols bgp group \"" + rscData.Name.ValueString() + "\" " + } + + configSet = append(configSet, + delPrefix+"accept-remote-nexthop", + delPrefix+"advertise-external", + delPrefix+"advertise-inactive", + delPrefix+"advertise-peer-as", + delPrefix+"no-advertise-peer-as", + delPrefix+"as-override", + delPrefix+"authentication-algorithm", + delPrefix+"authentication-key", + delPrefix+"authentication-key-chain", + delPrefix+"cluster", + delPrefix+"damping", + delPrefix+"export", + delPrefix+"hold-time", + delPrefix+"import", + delPrefix+"keep", + delPrefix+"local-address", + delPrefix+"local-as", + delPrefix+"local-interface", + delPrefix+"local-preference", + delPrefix+"log-updown", + delPrefix+"metric-out", + delPrefix+"mtu-discovery", + delPrefix+"multihop", + delPrefix+"multipath", + delPrefix+"out-delay", + delPrefix+"passive", + delPrefix+"peer-as", + delPrefix+"preference", + delPrefix+"remove-private", + delPrefix+"bfd-liveness-detection", + delPrefix+"family evpn", + delPrefix+"family inet", + delPrefix+"family inet6", + delPrefix+"graceful-restart", + ) + + return junSess.ConfigSet(configSet) +} + +func (rscData *bgpGroupData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := make([]string, 1) + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + configSet[0] = junos.DelRoutingInstances + v + + " protocols bgp group \"" + rscData.Name.ValueString() + "\"" + } else { + configSet[0] = junos.DeleteW + + " protocols bgp group \"" + rscData.Name.ValueString() + "\"" + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_bgp_group_test.go b/internal/providerfwk/resource_bgp_group_test.go similarity index 82% rename from internal/providersdk/resource_bgp_group_test.go rename to internal/providerfwk/resource_bgp_group_test.go index 1eb51f25..ed4890a5 100644 --- a/internal/providersdk/resource_bgp_group_test.go +++ b/internal/providerfwk/resource_bgp_group_test.go @@ -1,10 +1,10 @@ -package providersdk_test +package providerfwk_test import ( "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccJunosBgpGroup_basic(t *testing.T) { @@ -34,8 +34,6 @@ func TestAccJunosBgpGroup_basic(t *testing.T) { "log_updown", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "mtu_discovery", "true"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bgp_multipath.#", "1"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "remove_private", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", @@ -71,63 +69,47 @@ func TestAccJunosBgpGroup_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "import.0", "testacc_bgpgroup"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.detection_time_threshold", "60"), + "bfd_liveness_detection.detection_time_threshold", "60"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.transmit_interval_threshold", "30"), + "bfd_liveness_detection.transmit_interval_threshold", "30"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.transmit_interval_minimum_interval", "10"), + "bfd_liveness_detection.transmit_interval_minimum_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.holddown_interval", "10"), + "bfd_liveness_detection.holddown_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.minimum_interval", "10"), + "bfd_liveness_detection.minimum_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.minimum_receive_interval", "10"), + "bfd_liveness_detection.minimum_receive_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.multiplier", "2"), + "bfd_liveness_detection.multiplier", "2"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bfd_liveness_detection.0.session_mode", "automatic"), + "bfd_liveness_detection.session_mode", "automatic"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "family_inet.#", "2"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "family_inet.0.nlri_type", "unicast"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.accepted_prefix_limit.0.maximum", "2"), + "family_inet.0.accepted_prefix_limit.maximum", "2"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.accepted_prefix_limit.0.teardown", "50"), + "family_inet.0.accepted_prefix_limit.teardown", "50"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.accepted_prefix_limit.0.teardown_idle_timeout", "30"), + "family_inet.0.accepted_prefix_limit.teardown_idle_timeout", "30"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.prefix_limit.#", "1"), + "family_inet.0.prefix_limit.maximum", "2"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.prefix_limit.0.maximum", "2"), + "family_inet.0.prefix_limit.teardown", "50"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.prefix_limit.0.teardown", "50"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.0.prefix_limit.0.teardown_idle_timeout", "30"), + "family_inet.0.prefix_limit.teardown_idle_timeout", "30"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "family_inet.1.nlri_type", "multicast"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.1.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.1.accepted_prefix_limit.0.teardown_idle_timeout_forever", "true"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.1.prefix_limit.#", "1"), + "family_inet.1.accepted_prefix_limit.teardown_idle_timeout_forever", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet.1.prefix_limit.0.teardown_idle_timeout_forever", "true"), + "family_inet.1.prefix_limit.teardown_idle_timeout_forever", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "family_inet6.#", "2"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet6.0.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "family_inet6.0.prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "graceful_restart.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "graceful_restart.0.disable", "true"), + "graceful_restart.disable", "true"), ), }, { @@ -143,9 +125,7 @@ func TestAccJunosBgpGroup_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "advertise_external_conditional", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bgp_multipath.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "bgp_multipath.0.multiple_as", "true"), + "bgp_multipath.multiple_as", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "no_advertise_peer_as", "true"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", @@ -155,11 +135,9 @@ func TestAccJunosBgpGroup_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", "authentication_key", "password"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "graceful_restart.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "graceful_restart.0.restart_time", "10"), + "graceful_restart.restart_time", "10"), resource.TestCheckResourceAttr("junos_bgp_group.testacc_bgpgroup", - "graceful_restart.0.stale_route_time", "10"), + "graceful_restart.stale_route_time", "10"), ), }, { diff --git a/internal/providerfwk/resource_bgp_neighbor.go b/internal/providerfwk/resource_bgp_neighbor.go new file mode 100644 index 00000000..0234d7a6 --- /dev/null +++ b/internal/providerfwk/resource_bgp_neighbor.go @@ -0,0 +1,2020 @@ +package providerfwk + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfplanmodifier" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + "github.com/jeremmfr/terraform-provider-junos/internal/utils" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &bgpNeighbor{} + _ resource.ResourceWithConfigure = &bgpNeighbor{} + _ resource.ResourceWithModifyPlan = &bgpNeighbor{} + _ resource.ResourceWithValidateConfig = &bgpNeighbor{} + _ resource.ResourceWithImportState = &bgpNeighbor{} + _ resource.ResourceWithUpgradeState = &bgpNeighbor{} +) + +type bgpNeighbor struct { + client *junos.Client +} + +func newBgpNeighborResource() resource.Resource { + return &bgpNeighbor{} +} + +func (rsc *bgpNeighbor) typeName() string { + return providerName + "_bgp_neighbor" +} + +func (rsc *bgpNeighbor) junosName() string { + return "bgp neighbor" +} + +func (rsc *bgpNeighbor) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *bgpNeighbor) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *bgpNeighbor) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *bgpNeighbor) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Version: 1, + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format `_-__-_`.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "ip": schema.StringAttribute{ + Required: true, + Description: "IP of neighbor.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(junos.DefaultW), + Description: "Routing instance for bgp protocol if not root level.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "group": schema.StringAttribute{ + Required: true, + Description: "Name of BGP group for this neighbor.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "accept_remote_nexthop": schema.BoolAttribute{ + Optional: true, + Description: "Allow import policy to specify a non-directly connected next-hop.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_external": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Advertise best external routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_external_conditional": schema.BoolAttribute{ + Optional: true, + Description: "Route matches active route upto med-comparison rule.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_inactive": schema.BoolAttribute{ + Optional: true, + Description: "Advertise inactive routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "advertise_peer_as": schema.BoolAttribute{ + Optional: true, + Description: "Advertise routes received from the same autonomous system.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "no_advertise_peer_as": schema.BoolAttribute{ + Optional: true, + Description: "Don't advertise routes received from the same autonomous system.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "as_override": schema.BoolAttribute{ + Optional: true, + Description: "Replace neighbor AS number with our AS number.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + Description: "Authentication algorithm name.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "authentication_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + Description: "MD5 authentication key.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 126), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + Description: "Key chain name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "cluster": schema.StringAttribute{ + Optional: true, + Description: "Cluster identifier.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "damping": schema.BoolAttribute{ + Optional: true, + Description: "Enable route flap damping.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "export": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Export policy list.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "hold_time": schema.Int64Attribute{ + Optional: true, + Description: "Hold time used when negotiating with a peer.", + Validators: []validator.Int64{ + int64validator.Between(3, 65535), + }, + }, + "import": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Import policy list.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "keep_all": schema.BoolAttribute{ + Optional: true, + Description: "Retain all routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "keep_none": schema.BoolAttribute{ + Optional: true, + Description: "Retain no routes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_address": schema.StringAttribute{ + Optional: true, + Description: "Address of local end of BGP session.", + Validators: []validator.String{ + tfvalidator.StringIPAddress(), + }, + }, + "local_as": schema.StringAttribute{ + Optional: true, + Description: "Local autonomous system number.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "local_as_alias": schema.BoolAttribute{ + Optional: true, + Description: "Treat this AS as an alias to the system AS.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_as_loops": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of times this AS can be in an AS path (1..10).", + Validators: []validator.Int64{ + int64validator.Between(1, 10), + }, + }, + "local_as_no_prepend_global_as": schema.BoolAttribute{ + Optional: true, + Description: "Do not prepend global autonomous-system number in advertised paths.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_as_private": schema.BoolAttribute{ + Optional: true, + Description: "Hide this local AS in paths learned from this peering.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "local_interface": schema.StringAttribute{ + Optional: true, + Description: "Local interface for IPv6 link local EBGP peering.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.String1DotCount(), + }, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + Description: "Value of LOCAL_PREF path attribute.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "log_updown": schema.BoolAttribute{ + Optional: true, + Description: "Log a message for peer state transitions.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out": schema.Int64Attribute{ + Optional: true, + Description: "Route metric sent in MED.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "metric_out_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Track the IGP metric.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_igp_delay_med_update": schema.BoolAttribute{ + Optional: true, + Description: "Delay updating MED when IGP metric increases.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_igp_offset": schema.Int64Attribute{ + Optional: true, + Description: "Metric offset for MED.", + Validators: []validator.Int64{ + int64validator.Between(-2147483648, 2147483647), + }, + }, + "metric_out_minimum_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Track the minimum IGP metric.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "metric_out_minimum_igp_offset": schema.Int64Attribute{ + Optional: true, + Description: "Metric offset for MED.", + Validators: []validator.Int64{ + int64validator.Between(-2147483648, 2147483647), + }, + }, + "mtu_discovery": schema.BoolAttribute{ + Optional: true, + Description: "Enable TCP path MTU discovery.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "multihop": schema.BoolAttribute{ + Optional: true, + Description: "Configure an EBGP multihop session.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "out_delay": schema.Int64Attribute{ + Optional: true, + Description: "How long before exporting routes from routing table.", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + "passive": schema.BoolAttribute{ + Optional: true, + Description: "Do not send open messages to a peer.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "peer_as": schema.StringAttribute{ + Optional: true, + Description: "Autonomous system number.", + Validators: []validator.String{ + stringvalidator.RegexMatches(regexp.MustCompile(`^\d+(\.\d+)?$`), + "must be in plain number or `higher 16bits`.`lower 16 bits` (asdot notation) format"), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "remove_private": schema.BoolAttribute{ + Optional: true, + Description: "Remove well-known private AS numbers.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "bfd_liveness_detection": schema.SingleNestedBlock{ + Description: "Define Bidirectional Forwarding Detection (BFD) options.", + Attributes: map[string]schema.Attribute{ + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + Description: "Authentication algorithm name.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + Description: "Authentication key chain name.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 128), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "authentication_loose_check": schema.BoolAttribute{ + Optional: true, + Description: "Verify authentication only if authentication is negotiated.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "detection_time_threshold": schema.Int64Attribute{ + Optional: true, + Description: "High detection-time triggering a trap (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "holddown_interval": schema.Int64Attribute{ + Optional: true, + Description: "Time to hold the session-UP notification to the client (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "minimum_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum transmit and receive interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "minimum_receive_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum receive interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "multiplier": schema.Int64Attribute{ + Optional: true, + Description: "Detection time multiplier (1..255).", + Validators: []validator.Int64{ + int64validator.Between(1, 255), + }, + }, + "session_mode": schema.StringAttribute{ + Optional: true, + Description: "BFD single-hop or multihop session-mode.", + Validators: []validator.String{ + stringvalidator.OneOf("automatic", "multihop", "single-hop"), + }, + }, + "transmit_interval_minimum_interval": schema.Int64Attribute{ + Optional: true, + Description: "Minimum transmit interval (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 255000), + }, + }, + "transmit_interval_threshold": schema.Int64Attribute{ + Optional: true, + Description: "High transmit interval triggering a trap (milliseconds).", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "version": schema.StringAttribute{ + Optional: true, + Description: "BFD protocol version number.", + Validators: []validator.String{ + stringvalidator.OneOf("0", "1", "automatic"), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "bgp_multipath": schema.SingleNestedBlock{ + Description: "Allow load sharing among multiple BGP paths.", + Attributes: map[string]schema.Attribute{ + "allow_protection": schema.BoolAttribute{ + Optional: true, + Description: "Allows the BGP multipath and protection to co-exist.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "disable": schema.BoolAttribute{ + Optional: true, + Description: "Disable Multipath.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "multiple_as": schema.BoolAttribute{ + Optional: true, + Description: "Use paths received from different ASs.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "family_evpn": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure EVPN NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("signaling"), + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("signaling"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "family_inet": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure IPv4 NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("any", "flow", "labeled-unicast", "unicast", "multicast"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "family_inet6": schema.ListNestedBlock{ + Description: "For each `nlri_type`, configure IPv6 NLRI parameters.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + Description: "NLRI type.", + Validators: []validator.String{ + stringvalidator.OneOf("any", "flow", "labeled-unicast", "unicast", "multicast"), + }, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes accepted from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "prefix_limit": schema.SingleNestedBlock{ + Description: "Define maximum number of prefixes from a peer.", + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + "graceful_restart": schema.SingleNestedBlock{ + Description: "Define BGP graceful restart options.", + Attributes: map[string]schema.Attribute{ + "disable": schema.BoolAttribute{ + Optional: true, + Description: "Disable graceful restart.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "restart_time": schema.Int64Attribute{ + Optional: true, + Description: "Restart time used when negotiating with a peer (1..600).", + Validators: []validator.Int64{ + int64validator.Between(1, 600), + }, + }, + "stale_route_time": schema.Int64Attribute{ + Optional: true, + Description: "Maximum time for which stale routes are kept (1..600).", + Validators: []validator.Int64{ + int64validator.Between(1, 600), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + } +} + +func (rsc *bgpNeighbor) schemaFamilyPrefixLimitAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "maximum": schema.Int64Attribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Maximum number of prefixes accepted from a peer.", + Validators: []validator.Int64{ + int64validator.Between(1, 4294967295), + }, + }, + "teardown": schema.Int64Attribute{ + Optional: true, + Description: "Clear peer connection on reaching limit with this percentage of prefix-limit to start warnings.", + Validators: []validator.Int64{ + int64validator.Between(1, 100), + }, + }, + "teardown_idle_timeout": schema.Int64Attribute{ + Optional: true, + Description: "Timeout before attempting to restart peer.", + Validators: []validator.Int64{ + int64validator.Between(1, 2400), + }, + }, + "teardown_idle_timeout_forever": schema.BoolAttribute{ + Optional: true, + Description: "Idle the peer until the user intervenes.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + } +} + +type bgpNeighborData struct { + AcceptRemoteNexthop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemovePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export []types.String `tfsdk:"export"` + Group types.String `tfsdk:"group"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import []types.String `tfsdk:"import"` + IP types.String `tfsdk:"ip"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipath *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn []bgpBlockFamily `tfsdk:"family_evpn"` + FamilyInet []bgpBlockFamily `tfsdk:"family_inet"` + FamilyInet6 []bgpBlockFamily `tfsdk:"family_inet6"` + GracefulRestart *bgpBlockGracefulRestart `tfsdk:"graceful_restart"` +} + +type bgpNeighborConfig struct { + AcceptRemoteDesktop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemotePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export types.List `tfsdk:"export"` + Group types.String `tfsdk:"group"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import types.List `tfsdk:"import"` + IP types.String `tfsdk:"ip"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipah *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn types.List `tfsdk:"family_evpn"` + FamilyInet types.List `tfsdk:"family_inet"` + FamilyInet6 types.List `tfsdk:"family_inet6"` + GracefulRestart *bgpBlockGracefulRestart `tfsdk:"graceful_restart"` +} + +func (rsc *bgpNeighbor) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config bgpNeighborConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if !config.AdvertisePeerAS.IsNull() && + !config.NoAdvertisePeerAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("advertise_peer_as"), + tfdiag.ConflictConfigErrSummary, + "advertise_peer_as and no_advertise_peer_as can't be true in same time ", + ) + } + if !config.KeepAll.IsNull() && + !config.KeepNone.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("keep_all"), + tfdiag.ConflictConfigErrSummary, + "keep_all and keep_none can't be true in same time ", + ) + } + if !config.AuthenticationKey.IsNull() { + if !config.AuthenticationAlgorithm.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("authentication_algorithm"), + tfdiag.ConflictConfigErrSummary, + "authentication_algorithm and authentication_key cannot be configured together", + ) + } + if !config.AuthenticationKeyChain.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("authentication_key_chain"), + tfdiag.ConflictConfigErrSummary, + "authentication_key_chain and authentication_key cannot be configured together", + ) + } + } + if !config.LocalASAlias.IsNull() { + if !config.LocalASPrivate.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_alias"), + tfdiag.ConflictConfigErrSummary, + "local_as_alias and local_as_private cannot be configured together", + ) + } + if !config.LocalASNoPrependGlobalAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_alias"), + tfdiag.ConflictConfigErrSummary, + "local_as_alias and local_as_no_prepend_global_as cannot be configured together", + ) + } + } + if !config.LocalASPrivate.IsNull() { + if !config.LocalASNoPrependGlobalAS.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("local_as_private"), + tfdiag.ConflictConfigErrSummary, + "local_as_private and local_as_no_prepend_global_as cannot be configured together", + ) + } + } + if !config.MetricOut.IsNull() { + if !config.MetricOutIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp cannot be configured together", + ) + } + if !config.MetricOutIgpDelayMedUpdate.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp_delay_med_update"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp_delay_med_update cannot be configured together", + ) + } + if !config.MetricOutIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_igp_offset cannot be configured together", + ) + } + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgp.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgpDelayMedUpdate.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_delay_med_update and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_delay_med_update and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.MetricOutIgpOffset.IsNull() { + if !config.MetricOutMinimumIgp.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_offset and metric_out_minimum_igp cannot be configured together", + ) + } + if !config.MetricOutMinimumIgpOffset.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("metric_out_minimum_igp_offset"), + tfdiag.ConflictConfigErrSummary, + "metric_out_igp_offset and metric_out_minimum_igp_offset cannot be configured together", + ) + } + } + if !config.FamilyEvpn.IsNull() && !config.FamilyEvpn.IsUnknown() { + var configFamilyEvpn []bgpBlockFamily + asDiags := config.FamilyEvpn.ElementsAs(ctx, &configFamilyEvpn, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyEvpnNlriType := make(map[string]struct{}) + for i, block := range configFamilyEvpn { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyEvpnNlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_evpn blocks with the same nlri_type %q", nlriType), + ) + } else { + familyEvpnNlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_evpn block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_evpn block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_evpn block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_evpn").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_evpn block ", + ) + } + } + } + } + if !config.FamilyInet.IsNull() && !config.FamilyInet.IsUnknown() { + var configFamilyInet []bgpBlockFamily + asDiags := config.FamilyInet.ElementsAs(ctx, &configFamilyInet, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyInetNlriType := make(map[string]struct{}) + for i, block := range configFamilyInet { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyInetNlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_inet blocks with the same nlri_type %q", nlriType), + ) + } else { + familyInetNlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_inet block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_inet block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_inet block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_inet block ", + ) + } + } + } + } + if !config.FamilyInet6.IsNull() && !config.FamilyInet6.IsUnknown() { + var configFamilyInet6 []bgpBlockFamily + asDiags := config.FamilyInet6.ElementsAs(ctx, &configFamilyInet6, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + familyInet6NlriType := make(map[string]struct{}) + for i, block := range configFamilyInet6 { + if !block.NlriType.IsUnknown() { + nlriType := block.NlriType.ValueString() + if _, ok := familyInet6NlriType[nlriType]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("nlri_type"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple family_inet6 blocks with the same nlri_type %q", nlriType), + ) + } else { + familyInet6NlriType[nlriType] = struct{}{} + } + } + if block.AcceptedPrefixLimit != nil { + if block.AcceptedPrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("accepted_prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in accepted_prefix_limit block in family_inet6 block", + ) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() && + !block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("accepted_prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in accepted_prefix_limit block in family_inet6 block ", + ) + } + } + if block.PrefixLimit != nil { + if block.PrefixLimit.Maximum.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("prefix_limit").AtName("maximum"), + tfdiag.MissingConfigErrSummary, + "maximum must be specified in prefix_limit block in family_inet6 block", + ) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() && + !block.PrefixLimit.TeardownIdleTimeoutForever.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("family_inet6").AtListIndex(i).AtName("prefix_limit").AtName("teardown_idle_timeout"), + tfdiag.ConflictConfigErrSummary, + "teardown_idle_timeout and teardown_idle_timeout_forever cannot be configured together"+ + " in prefix_limit block family_inet6 block ", + ) + } + } + } + } + if config.GracefulRestart != nil { + if !config.GracefulRestart.Disable.IsNull() { + if !config.GracefulRestart.RestartTime.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("graceful_restart").AtName("restart_time"), + tfdiag.ConflictConfigErrSummary, + "restart_time and disable cannot be configured together"+ + " in graceful_restart block", + ) + } + if !config.GracefulRestart.StaleRouteTime.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("graceful_restart").AtName("stale_route_time"), + tfdiag.ConflictConfigErrSummary, + "stale_route_time and disable cannot be configured together"+ + " in graceful_restart block", + ) + } + } + } +} + +func (rsc *bgpNeighbor) ModifyPlan( + ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse, +) { + if req.Plan.Raw.IsNull() { + return + } + + var config, plan bgpNeighborConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if config.AdvertiseExternal.IsNull() { + if config.AdvertiseExternalConditional.IsNull() { + plan.AdvertiseExternal = types.BoolNull() + } else if !plan.AdvertiseExternalConditional.IsNull() && + !plan.AdvertiseExternalConditional.IsUnknown() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if config.MetricOutIgp.IsNull() { + if config.MetricOutIgpDelayMedUpdate.IsNull() && + config.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolNull() + } else { + if !plan.MetricOutIgpDelayMedUpdate.IsNull() && + !plan.MetricOutIgpDelayMedUpdate.IsUnknown() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() && + !plan.MetricOutIgpOffset.IsUnknown() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + } + if config.MetricOutMinimumIgp.IsNull() { + if config.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolNull() + } else if !plan.MetricOutMinimumIgpOffset.IsNull() && + !plan.MetricOutMinimumIgpOffset.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...) +} + +func (rsc *bgpNeighbor) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan bgpNeighborData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.IP.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("ip"), + "Empty ip", + "could not create "+rsc.junosName()+" with empty ip", + ) + + return + } + if plan.Group.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("group"), + "Empty group", + "could not create "+rsc.junosName()+" with empty group", + ) + + return + } + + if plan.AdvertiseExternal.IsUnknown() { + plan.AdvertiseExternal = types.BoolNull() + if plan.AdvertiseExternalConditional.ValueBool() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if plan.MetricOutIgp.IsUnknown() { + plan.MetricOutIgp = types.BoolNull() + if plan.MetricOutIgpDelayMedUpdate.ValueBool() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + if plan.MetricOutMinimumIgp.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolNull() + if !plan.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + instanceExists, err := checkRoutingInstanceExists(fnCtx, v, junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !instanceExists { + resp.Diagnostics.AddAttributeError( + path.Root("routing_instance"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("routing instance %q doesn't exist", v), + ) + + return false + } + } + bgpGroupExists, err := checkBgpGroupExists( + fnCtx, + plan.Group.ValueString(), + plan.RoutingInstance.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if !bgpGroupExists { + resp.Diagnostics.AddAttributeError( + path.Root("group"), + tfdiag.PreCheckErrSummary, + fmt.Sprintf("bgp group %q doesn't exist", plan.Group.ValueString()), + ) + + return false + } + bgpNeighborExists, err := checkBgpNeighborExists( + fnCtx, + plan.IP.ValueString(), + plan.RoutingInstance.ValueString(), + plan.Group.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if bgpNeighborExists { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists in group %q in routing-instance %q", + plan.IP.ValueString(), plan.Group.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists in group %q", + plan.IP.ValueString(), plan.Group.ValueString()), + ) + } + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + bgpNeighborExists, err := checkBgpNeighborExists( + fnCtx, + plan.IP.ValueString(), + plan.RoutingInstance.ValueString(), + plan.Group.ValueString(), + junSess, + ) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !bgpNeighborExists { + if v := plan.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists in group %q in routing-instance %q after commit "+ + "=> check your config", plan.IP.ValueString(), plan.Group.ValueString(), v), + ) + } else { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists in group %q after commit "+ + "=> check your config", plan.IP.ValueString(), plan.Group.ValueString()), + ) + } + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *bgpNeighbor) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data bgpNeighborData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom3String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.IP.ValueString(), + state.RoutingInstance.ValueString(), + state.Group.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *bgpNeighbor) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state bgpNeighborData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if plan.AdvertiseExternal.IsUnknown() { + plan.AdvertiseExternal = types.BoolNull() + if plan.AdvertiseExternalConditional.ValueBool() { + plan.AdvertiseExternal = types.BoolValue(true) + } + } + if plan.MetricOutIgp.IsUnknown() { + plan.MetricOutIgp = types.BoolNull() + if plan.MetricOutIgpDelayMedUpdate.ValueBool() { + plan.MetricOutIgp = types.BoolValue(true) + } + if !plan.MetricOutIgpOffset.IsNull() { + plan.MetricOutIgp = types.BoolValue(true) + } + } + if plan.MetricOutMinimumIgp.IsUnknown() { + plan.MetricOutMinimumIgp = types.BoolNull() + if !plan.MetricOutMinimumIgpOffset.IsNull() { + plan.MetricOutMinimumIgp = types.BoolValue(true) + } + } + + var _ resourceDataDelWithOpts = &state + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *bgpNeighbor) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state bgpNeighborData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *bgpNeighbor) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data bgpNeighborData + + var _ resourceDataReadFrom3String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be "+junos.IDSeparator+""+junos.IDSeparator+")", req.ID), + ) +} + +func checkBgpNeighborExists( + _ context.Context, + ip, + routingInstance, + group string, + junSess *junos.Session, +) ( + _ bool, err error, +) { + var showConfig string + if routingInstance == junos.DefaultW || routingInstance == "" { + showConfig, err = junSess.Command(junos.CmdShowConfig + + "protocols bgp group \"" + group + "\"" + + " neighbor " + ip + junos.PipeDisplaySet) + if err != nil { + return false, err + } + } else { + showConfig, err = junSess.Command(junos.CmdShowConfig + + junos.RoutingInstancesWS + routingInstance + " " + + "protocols bgp group \"" + group + "\"" + + " neighbor " + ip + junos.PipeDisplaySet) + if err != nil { + return false, err + } + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *bgpNeighborData) fillID() { + if v := rscData.RoutingInstance.ValueString(); v != "" { + rscData.ID = types.StringValue( + rscData.IP.ValueString() + junos.IDSeparator + + v + junos.IDSeparator + + rscData.Group.ValueString(), + ) + } else { + rscData.ID = types.StringValue( + rscData.IP.ValueString() + junos.IDSeparator + + junos.DefaultW + junos.IDSeparator + + rscData.Group.ValueString(), + ) + } +} + +func (rscData *bgpNeighborData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *bgpNeighborData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + setPrefix := "set protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + " " + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + setPrefix = junos.SetRoutingInstances + v + + " protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + " " + } + configSet := []string{ + setPrefix, + } + + if rscData.AcceptRemoteNexthop.ValueBool() { + configSet = append(configSet, setPrefix+"accept-remote-nexthop") + } + if rscData.AdvertiseExternal.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-external") + } + if rscData.AdvertiseExternalConditional.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-external conditional") + } + if rscData.AdvertiseInactive.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-inactive") + } + if rscData.AdvertisePeerAS.ValueBool() { + configSet = append(configSet, setPrefix+"advertise-peer-as") + } + if rscData.NoAdvertisePeerAS.ValueBool() { + configSet = append(configSet, setPrefix+"no-advertise-peer-as") + } + if rscData.ASOverride.ValueBool() { + configSet = append(configSet, setPrefix+"as-override") + } + if v := rscData.AuthenticationAlgorithm.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-algorithm "+v) + } + if v := rscData.AuthenticationKey.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-key \""+v+"\"") + } + if v := rscData.AuthenticationKeyChain.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication-key-chain \""+v+"\"") + } + if v := rscData.Cluster.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"cluster "+v) + } + if rscData.Damping.ValueBool() { + configSet = append(configSet, setPrefix+"damping") + } + for _, v := range rscData.Export { + configSet = append(configSet, setPrefix+"export "+v.ValueString()) + } + if !rscData.HoldTime.IsNull() { + configSet = append(configSet, setPrefix+"hold-time "+ + utils.ConvI64toa(rscData.HoldTime.ValueInt64())) + } + for _, v := range rscData.Import { + configSet = append(configSet, setPrefix+"import "+v.ValueString()) + } + if rscData.KeepAll.ValueBool() { + configSet = append(configSet, setPrefix+"keep all") + } + if rscData.KeepNone.ValueBool() { + configSet = append(configSet, setPrefix+"keep none") + } + if v := rscData.LocalAddress.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-address "+v) + } + if v := rscData.LocalAS.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-as "+v) + } + if rscData.LocalASAlias.ValueBool() { + configSet = append(configSet, setPrefix+"local-as alias") + } + if !rscData.LocalASLoops.IsNull() { + configSet = append(configSet, setPrefix+"local-as loops "+ + utils.ConvI64toa(rscData.LocalASLoops.ValueInt64())) + } + if rscData.LocalASNoPrependGlobalAS.ValueBool() { + configSet = append(configSet, setPrefix+"local-as no-prepend-global-as") + } + if rscData.LocalASPrivate.ValueBool() { + configSet = append(configSet, setPrefix+"local-as private") + } + if v := rscData.LocalInterface.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"local-interface "+v) + } + if !rscData.LocalPreference.IsNull() { + configSet = append(configSet, setPrefix+"local-preference "+ + utils.ConvI64toa(rscData.LocalPreference.ValueInt64())) + } + if rscData.LogUpdown.ValueBool() { + configSet = append(configSet, setPrefix+"log-updown") + } + if !rscData.MetricOut.IsNull() { + configSet = append(configSet, setPrefix+"metric-out "+ + utils.ConvI64toa(rscData.MetricOut.ValueInt64())) + } + if rscData.MetricOutIgp.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out igp") + } + if rscData.MetricOutIgpDelayMedUpdate.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out igp delay-med-update") + } + if !rscData.MetricOutIgpOffset.IsNull() { + configSet = append(configSet, setPrefix+"metric-out igp "+ + utils.ConvI64toa(rscData.MetricOutIgpOffset.ValueInt64())) + } + if rscData.MetricOutMinimumIgp.ValueBool() { + configSet = append(configSet, setPrefix+"metric-out minimum-igp") + } + if !rscData.MetricOutMinimumIgpOffset.IsNull() { + configSet = append(configSet, setPrefix+"metric-out minimum-igp "+ + utils.ConvI64toa(rscData.MetricOutMinimumIgpOffset.ValueInt64())) + } + if rscData.MtuDiscovery.ValueBool() { + configSet = append(configSet, setPrefix+"mtu-discovery") + } + if rscData.Multihop.ValueBool() { + configSet = append(configSet, setPrefix+"multihop") + } + if !rscData.OutDelay.IsNull() { + configSet = append(configSet, setPrefix+"out-delay "+ + utils.ConvI64toa(rscData.OutDelay.ValueInt64())) + } + if rscData.Passive.ValueBool() { + configSet = append(configSet, setPrefix+"passive") + } + if v := rscData.PeerAS.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"peer-as "+v) + } + if !rscData.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(rscData.Preference.ValueInt64())) + } + if rscData.RemovePrivate.ValueBool() { + configSet = append(configSet, setPrefix+"remove-private") + } + if rscData.BfdLivenessDetection != nil { + if rscData.BfdLivenessDetection.isEmpty() { + return path.Root("bfd_liveness_detection").AtName("*"), + fmt.Errorf("bfd_liveness_detection block is empty") + } + + configSet = append(configSet, rscData.BfdLivenessDetection.configSet(setPrefix)...) + } + if rscData.BgpMultipath != nil { + configSet = append(configSet, rscData.BgpMultipath.configSet(setPrefix)...) + } + familyEvpnNlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyEvpn { + nlriType := block.NlriType.ValueString() + if _, ok := familyEvpnNlriType[nlriType]; ok { + return path.Root("family_evpn").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_evpn blocks with the same nlri_type %q", nlriType) + } + familyEvpnNlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family evpn ", path.Root("family_evpn").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + familyInetNlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyInet { + nlriType := block.NlriType.ValueString() + if _, ok := familyInetNlriType[nlriType]; ok { + return path.Root("family_inet").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_inet blocks with the same nlri_type %q", nlriType) + } + familyInetNlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family inet ", path.Root("family_inet").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + familyInet6NlriType := make(map[string]struct{}) + for i, block := range rscData.FamilyInet6 { + nlriType := block.NlriType.ValueString() + if _, ok := familyInet6NlriType[nlriType]; ok { + return path.Root("family_inet6").AtListIndex(i).AtName("nlri_type"), + fmt.Errorf("multiple family_inet6 blocks with the same nlri_type %q", nlriType) + } + familyInet6NlriType[nlriType] = struct{}{} + + blockSet, pathErr, err := block.configSet(setPrefix+"family inet6 ", path.Root("family_inet6").AtListIndex(i)) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + if rscData.GracefulRestart != nil { + configSet = append(configSet, rscData.GracefulRestart.configSet(setPrefix)...) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *bgpNeighborData) read( + _ context.Context, + ip, + routingInstance, + group string, + junSess *junos.Session, +) ( + err error, +) { + var showConfig string + if routingInstance == junos.DefaultW { + showConfig, err = junSess.Command(junos.CmdShowConfig + + "protocols bgp group \"" + group + "\"" + + " neighbor " + ip + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + } else { + showConfig, err = junSess.Command(junos.CmdShowConfig + + junos.RoutingInstancesWS + routingInstance + " " + + "protocols bgp group \"" + group + "\"" + + " neighbor " + ip + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + } + if showConfig != junos.EmptyW { + rscData.IP = types.StringValue(ip) + if routingInstance == "" { + rscData.RoutingInstance = types.StringValue(junos.DefaultW) + } else { + rscData.RoutingInstance = types.StringValue(routingInstance) + } + rscData.Group = types.StringValue(group) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case itemTrim == "accept-remote-nexthop": + rscData.AcceptRemoteNexthop = types.BoolValue(true) + case itemTrim == "advertise-external": + rscData.AdvertiseExternal = types.BoolValue(true) + case itemTrim == "advertise-external conditional": + rscData.AdvertiseExternal = types.BoolValue(true) + rscData.AdvertiseExternalConditional = types.BoolValue(true) + case itemTrim == "advertise-inactive": + rscData.AdvertiseInactive = types.BoolValue(true) + case itemTrim == "advertise-peer-as": + rscData.AdvertisePeerAS = types.BoolValue(true) + case itemTrim == "no-advertise-peer-as": + rscData.NoAdvertisePeerAS = types.BoolValue(true) + case itemTrim == "as-override": + rscData.ASOverride = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "authentication-algorithm "): + rscData.AuthenticationAlgorithm = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "authentication-key "): + rscData.AuthenticationKey, err = tfdata.JunosDecode(strings.Trim(itemTrim, "\""), "authentication-key") + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "authentication-key-chain "): + rscData.AuthenticationKeyChain = types.StringValue(strings.Trim(itemTrim, "\"")) + case balt.CutPrefixInString(&itemTrim, "cluster "): + rscData.Cluster = types.StringValue(itemTrim) + case itemTrim == "damping": + rscData.Damping = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "export "): + rscData.Export = append(rscData.Export, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "hold-time "): + rscData.HoldTime, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "import "): + rscData.Import = append(rscData.Import, types.StringValue(itemTrim)) + case itemTrim == "keep all": + rscData.KeepAll = types.BoolValue(true) + case itemTrim == "keep none": + rscData.KeepNone = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "local-address "): + rscData.LocalAddress = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "local-as "): + switch { + case itemTrim == "private": + rscData.LocalASPrivate = types.BoolValue(true) + case itemTrim == "alias": + rscData.LocalASAlias = types.BoolValue(true) + case itemTrim == "no-prepend-global-as": + rscData.LocalASNoPrependGlobalAS = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "loops "): + rscData.LocalASLoops, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + default: + rscData.LocalAS = types.StringValue(itemTrim) + } + case balt.CutPrefixInString(&itemTrim, "local-interface "): + rscData.LocalInterface = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "local-preference "): + rscData.LocalPreference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "log-updown": + rscData.LogUpdown = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "metric-out "): + switch { + case balt.CutPrefixInString(&itemTrim, "igp"): + rscData.MetricOutIgp = types.BoolValue(true) + switch { + case itemTrim == " delay-med-update": + rscData.MetricOutIgpDelayMedUpdate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, " "): + rscData.MetricOutIgpOffset, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case balt.CutPrefixInString(&itemTrim, "minimum-igp"): + rscData.MetricOutMinimumIgp = types.BoolValue(true) + if balt.CutPrefixInString(&itemTrim, " ") { + rscData.MetricOutMinimumIgpOffset, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + default: + rscData.MetricOut, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case itemTrim == "mtu-discovery": + rscData.MtuDiscovery = types.BoolValue(true) + case itemTrim == "multihop": + rscData.Multihop = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "out-delay "): + rscData.OutDelay, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "passive": + rscData.Passive = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "peer-as "): + rscData.PeerAS = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "preference "): + rscData.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == "remove-private": + rscData.RemovePrivate = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): + if rscData.BfdLivenessDetection == nil { + rscData.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{} + } + if err := rscData.BfdLivenessDetection.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "family evpn "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyEvpn bgpBlockFamily + rscData.FamilyEvpn, familyEvpn = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyEvpn, "NlriType", itemTrimFields[0], + ) + familyEvpn.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyEvpn.read(itemTrim); err != nil { + return err + } + rscData.FamilyEvpn = append(rscData.FamilyEvpn, familyEvpn) + case balt.CutPrefixInString(&itemTrim, "family inet "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyInet bgpBlockFamily + rscData.FamilyInet, familyInet = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyInet, "NlriType", itemTrimFields[0], + ) + familyInet.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyInet.read(itemTrim); err != nil { + return err + } + rscData.FamilyInet = append(rscData.FamilyInet, familyInet) + case balt.CutPrefixInString(&itemTrim, "family inet6 "): + itemTrimFields := strings.Split(itemTrim, " ") + var familyInet6 bgpBlockFamily + rscData.FamilyInet6, familyInet6 = tfdata.ExtractBlockWithTFTypesString( + rscData.FamilyInet6, "NlriType", itemTrimFields[0], + ) + familyInet6.NlriType = types.StringValue(itemTrimFields[0]) + balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + if err := familyInet6.read(itemTrim); err != nil { + return err + } + rscData.FamilyInet6 = append(rscData.FamilyInet6, familyInet6) + case balt.CutPrefixInString(&itemTrim, "multipath"): + if rscData.BgpMultipath == nil { + rscData.BgpMultipath = &bgpBlockBgpMultipath{} + } + rscData.BgpMultipath.read(itemTrim) + case balt.CutPrefixInString(&itemTrim, "graceful-restart"): + if rscData.GracefulRestart == nil { + rscData.GracefulRestart = &bgpBlockGracefulRestart{} + } + if err := rscData.GracefulRestart.read(itemTrim); err != nil { + return err + } + } + } + } + + return nil +} + +func (rscData *bgpNeighborData) delOpts( + _ context.Context, junSess *junos.Session, +) error { + configSet := make([]string, 0) + delPrefix := junos.DeleteW + + " protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + " " + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + delPrefix = junos.DelRoutingInstances + v + + " protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + " " + } + + configSet = append(configSet, + delPrefix+"accept-remote-nexthop", + delPrefix+"advertise-external", + delPrefix+"advertise-inactive", + delPrefix+"advertise-peer-as", + delPrefix+"no-advertise-peer-as", + delPrefix+"as-override", + delPrefix+"authentication-algorithm", + delPrefix+"authentication-key", + delPrefix+"authentication-key-chain", + delPrefix+"cluster", + delPrefix+"damping", + delPrefix+"export", + delPrefix+"hold-time", + delPrefix+"import", + delPrefix+"keep", + delPrefix+"local-address", + delPrefix+"local-as", + delPrefix+"local-interface", + delPrefix+"local-preference", + delPrefix+"log-updown", + delPrefix+"metric-out", + delPrefix+"mtu-discovery", + delPrefix+"multihop", + delPrefix+"multipath", + delPrefix+"out-delay", + delPrefix+"passive", + delPrefix+"peer-as", + delPrefix+"preference", + delPrefix+"remove-private", + delPrefix+"bfd-liveness-detection", + delPrefix+"family evpn", + delPrefix+"family inet", + delPrefix+"family inet6", + delPrefix+"graceful-restart", + ) + + return junSess.ConfigSet(configSet) +} + +func (rscData *bgpNeighborData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := make([]string, 1) + if v := rscData.RoutingInstance.ValueString(); v != "" && v != junos.DefaultW { + configSet[0] = junos.DelRoutingInstances + v + + " protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + } else { + configSet[0] = junos.DeleteW + + " protocols bgp group \"" + rscData.Group.ValueString() + "\"" + + " neighbor " + rscData.IP.ValueString() + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/resource_bgp_neighbor_test.go b/internal/providerfwk/resource_bgp_neighbor_test.go similarity index 84% rename from internal/providersdk/resource_bgp_neighbor_test.go rename to internal/providerfwk/resource_bgp_neighbor_test.go index d1ec62be..2eca69b7 100644 --- a/internal/providersdk/resource_bgp_neighbor_test.go +++ b/internal/providerfwk/resource_bgp_neighbor_test.go @@ -1,10 +1,10 @@ -package providersdk_test +package providerfwk_test import ( "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccJunosBgpNeighbor_basic(t *testing.T) { @@ -34,8 +34,6 @@ func TestAccJunosBgpNeighbor_basic(t *testing.T) { "log_updown", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "mtu_discovery", "true"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bgp_multipath.#", "1"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "remove_private", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", @@ -71,63 +69,47 @@ func TestAccJunosBgpNeighbor_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "import.0", "testacc_bgpneighbor"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.detection_time_threshold", "60"), + "bfd_liveness_detection.detection_time_threshold", "60"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.transmit_interval_threshold", "30"), + "bfd_liveness_detection.transmit_interval_threshold", "30"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.transmit_interval_minimum_interval", "10"), + "bfd_liveness_detection.transmit_interval_minimum_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.holddown_interval", "10"), + "bfd_liveness_detection.holddown_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.minimum_interval", "10"), + "bfd_liveness_detection.minimum_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.minimum_receive_interval", "10"), + "bfd_liveness_detection.minimum_receive_interval", "10"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.multiplier", "2"), + "bfd_liveness_detection.multiplier", "2"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bfd_liveness_detection.0.session_mode", "automatic"), + "bfd_liveness_detection.session_mode", "automatic"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "family_inet.#", "2"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "family_inet.0.nlri_type", "unicast"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.accepted_prefix_limit.0.maximum", "2"), + "family_inet.0.accepted_prefix_limit.maximum", "2"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.accepted_prefix_limit.0.teardown", "50"), + "family_inet.0.accepted_prefix_limit.teardown", "50"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.accepted_prefix_limit.0.teardown_idle_timeout", "30"), + "family_inet.0.accepted_prefix_limit.teardown_idle_timeout", "30"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.prefix_limit.#", "1"), + "family_inet.0.prefix_limit.maximum", "2"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.prefix_limit.0.maximum", "2"), + "family_inet.0.prefix_limit.teardown", "50"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.prefix_limit.0.teardown", "50"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.0.prefix_limit.0.teardown_idle_timeout", "30"), + "family_inet.0.prefix_limit.teardown_idle_timeout", "30"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "family_inet.1.nlri_type", "multicast"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.1.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.1.accepted_prefix_limit.0.teardown_idle_timeout_forever", "true"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.1.prefix_limit.#", "1"), + "family_inet.1.accepted_prefix_limit.teardown_idle_timeout_forever", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet.1.prefix_limit.0.teardown_idle_timeout_forever", "true"), + "family_inet.1.prefix_limit.teardown_idle_timeout_forever", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "family_inet6.#", "2"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet6.0.accepted_prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "family_inet6.0.prefix_limit.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "graceful_restart.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "graceful_restart.0.disable", "true"), + "graceful_restart.disable", "true"), ), }, { @@ -145,9 +127,7 @@ func TestAccJunosBgpNeighbor_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "advertise_external_conditional", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bgp_multipath.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "bgp_multipath.0.multiple_as", "true"), + "bgp_multipath.multiple_as", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "no_advertise_peer_as", "true"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", @@ -157,11 +137,9 @@ func TestAccJunosBgpNeighbor_basic(t *testing.T) { resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", "authentication_key", "password"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "graceful_restart.#", "1"), - resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "graceful_restart.0.restart_time", "10"), + "graceful_restart.restart_time", "10"), resource.TestCheckResourceAttr("junos_bgp_neighbor.testacc_bgpneighbor", - "graceful_restart.0.stale_route_time", "10"), + "graceful_restart.stale_route_time", "10"), ), }, { diff --git a/internal/providerfwk/resourcedata_bgp.go b/internal/providerfwk/resourcedata_bgp.go new file mode 100644 index 00000000..fb63dff3 --- /dev/null +++ b/internal/providerfwk/resourcedata_bgp.go @@ -0,0 +1,375 @@ +package providerfwk + +import ( + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" + "github.com/jeremmfr/terraform-provider-junos/internal/utils" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +type bgpBlockBfdLivenessDetection struct { + AuthenticationLooseCheck types.Bool `tfsdk:"authentication_loose_check"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + DetectionTimeThreshold types.Int64 `tfsdk:"detection_time_threshold"` + HolddownInterval types.Int64 `tfsdk:"holddown_interval"` + MinimumInterval types.Int64 `tfsdk:"minimum_interval"` + MinimumReceiveInterval types.Int64 `tfsdk:"minimum_receive_interval"` + Multiplier types.Int64 `tfsdk:"multiplier"` + SessionMode types.String `tfsdk:"session_mode"` + TransmitIntervalMinimumInterval types.Int64 `tfsdk:"transmit_interval_minimum_interval"` + TransmitIntervalThreshold types.Int64 `tfsdk:"transmit_interval_threshold"` + Version types.String `tfsdk:"version"` +} + +func (block *bgpBlockBfdLivenessDetection) isEmpty() bool { + switch { + case !block.AuthenticationLooseCheck.IsNull(): + return false + case !block.AuthenticationAlgorithm.IsNull(): + return false + case !block.AuthenticationKeyChain.IsNull(): + return false + case !block.DetectionTimeThreshold.IsNull(): + return false + case !block.HolddownInterval.IsNull(): + return false + case !block.MinimumInterval.IsNull(): + return false + case !block.MinimumReceiveInterval.IsNull(): + return false + case !block.Multiplier.IsNull(): + return false + case !block.SessionMode.IsNull(): + return false + case !block.TransmitIntervalMinimumInterval.IsNull(): + return false + case !block.TransmitIntervalThreshold.IsNull(): + return false + case !block.Version.IsNull(): + return false + default: + return true + } +} + +type bgpBlockBgpMultipath struct { + AllowProtection types.Bool `tfsdk:"allow_protection"` + Disable types.Bool `tfsdk:"disable"` + MultipleAS types.Bool `tfsdk:"multiple_as"` +} + +type bgpBlockFamily struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit *bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit *bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` +} + +type bgpBlockFamilyBlockPrefixLimit struct { + TeardownIdleTimeoutForever types.Bool `tfsdk:"teardown_idle_timeout_forever"` + Maximum types.Int64 `tfsdk:"maximum"` + Teardown types.Int64 `tfsdk:"teardown"` + TeardownIdleTimeout types.Int64 `tfsdk:"teardown_idle_timeout"` +} + +type bgpBlockGracefulRestart struct { + Disable types.Bool `tfsdk:"disable"` + RestartTime types.Int64 `tfsdk:"restart_time"` + StaleRouteTime types.Int64 `tfsdk:"stale_route_time"` +} + +func (block *bgpBlockBfdLivenessDetection) configSet(setPrefix string) []string { + configSet := make([]string, 0) + setPrefix += "bfd-liveness-detection " + + if v := block.AuthenticationAlgorithm.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication algorithm "+v) + } + if v := block.AuthenticationKeyChain.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"authentication key-chain \""+v+"\"") + } + if block.AuthenticationLooseCheck.ValueBool() { + configSet = append(configSet, setPrefix+"authentication loose-check") + } + if !block.DetectionTimeThreshold.IsNull() { + configSet = append(configSet, setPrefix+"detection-time threshold "+ + utils.ConvI64toa(block.DetectionTimeThreshold.ValueInt64())) + } + if !block.HolddownInterval.IsNull() { + configSet = append(configSet, setPrefix+"holddown-interval "+ + utils.ConvI64toa(block.HolddownInterval.ValueInt64())) + } + if !block.MinimumInterval.IsNull() { + configSet = append(configSet, setPrefix+"minimum-interval "+ + utils.ConvI64toa(block.MinimumInterval.ValueInt64())) + } + if !block.MinimumReceiveInterval.IsNull() { + configSet = append(configSet, setPrefix+"minimum-receive-interval "+ + utils.ConvI64toa(block.MinimumReceiveInterval.ValueInt64())) + } + if !block.Multiplier.IsNull() { + configSet = append(configSet, setPrefix+"multiplier "+ + utils.ConvI64toa(block.Multiplier.ValueInt64())) + } + if v := block.SessionMode.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"session-mode "+v) + } + if !block.TransmitIntervalMinimumInterval.IsNull() { + configSet = append(configSet, setPrefix+"transmit-interval minimum-interval "+ + utils.ConvI64toa(block.TransmitIntervalMinimumInterval.ValueInt64())) + } + if !block.TransmitIntervalThreshold.IsNull() { + configSet = append(configSet, setPrefix+"transmit-interval threshold "+ + utils.ConvI64toa(block.TransmitIntervalThreshold.ValueInt64())) + } + if v := block.Version.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"version "+v) + } + + return configSet +} + +func (block *bgpBlockBgpMultipath) configSet(setPrefix string) []string { + setPrefix += "multipath" + configSet := []string{ + setPrefix, + } + + if block.AllowProtection.ValueBool() { + configSet = append(configSet, setPrefix+" allow-protection") + } + if block.Disable.ValueBool() { + configSet = append(configSet, setPrefix+" disable") + } + if block.MultipleAS.ValueBool() { + configSet = append(configSet, setPrefix+" multiple-as") + } + + return configSet +} + +func (block *bgpBlockFamily) configSet( + setPrefix string, pathRoot path.Path, +) ( + []string, // configSet + path.Path, // pathErr + error, // error +) { + setPrefix += block.NlriType.ValueString() + " " + configSet := []string{ + setPrefix, + } + + if block.AcceptedPrefixLimit != nil { + if !block.AcceptedPrefixLimit.Maximum.IsNull() { + configSet = append(configSet, setPrefix+"accepted-prefix-limit maximum "+ + utils.ConvI64toa(block.AcceptedPrefixLimit.Maximum.ValueInt64())) + } + if !block.AcceptedPrefixLimit.Teardown.IsNull() { + configSet = append(configSet, setPrefix+"accepted-prefix-limit teardown "+ + utils.ConvI64toa(block.AcceptedPrefixLimit.Teardown.ValueInt64())) + } + if !block.AcceptedPrefixLimit.TeardownIdleTimeout.IsNull() { + if block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.ValueBool() { + return configSet, + pathRoot.AtName("accepted_prefix_limit").AtName("teardown_idle_timeout_forever"), + fmt.Errorf("conflict between teardown_idle_timeout and teardown_idle_timeout_forever") + } + configSet = append(configSet, setPrefix+"accepted-prefix-limit teardown idle-timeout "+ + utils.ConvI64toa(block.AcceptedPrefixLimit.TeardownIdleTimeout.ValueInt64())) + } + if block.AcceptedPrefixLimit.TeardownIdleTimeoutForever.ValueBool() { + configSet = append(configSet, setPrefix+"accepted-prefix-limit teardown idle-timeout forever") + } + } + if block.PrefixLimit != nil { + if !block.PrefixLimit.Maximum.IsNull() { + configSet = append(configSet, setPrefix+"prefix-limit maximum "+ + utils.ConvI64toa(block.PrefixLimit.Maximum.ValueInt64())) + } + if !block.PrefixLimit.Teardown.IsNull() { + configSet = append(configSet, setPrefix+"prefix-limit teardown "+ + utils.ConvI64toa(block.PrefixLimit.Teardown.ValueInt64())) + } + if !block.PrefixLimit.TeardownIdleTimeout.IsNull() { + if block.PrefixLimit.TeardownIdleTimeoutForever.ValueBool() { + return configSet, + pathRoot.AtName("prefix_limit").AtName("teardown_idle_timeout_forever"), + fmt.Errorf("conflict between teardown_idle_timeout and teardown_idle_timeout_forever") + } + configSet = append(configSet, setPrefix+"prefix-limit teardown idle-timeout "+ + utils.ConvI64toa(block.PrefixLimit.TeardownIdleTimeout.ValueInt64())) + } + if block.PrefixLimit.TeardownIdleTimeoutForever.ValueBool() { + configSet = append(configSet, setPrefix+"prefix-limit teardown idle-timeout forever") + } + } + + return configSet, path.Empty(), nil +} + +func (block *bgpBlockGracefulRestart) configSet(setPrefix string) []string { + setPrefix += "graceful-restart" + configSet := []string{ + setPrefix, + } + + if block.Disable.ValueBool() { + configSet = append(configSet, setPrefix+" disable") + } + if !block.RestartTime.IsNull() { + configSet = append(configSet, setPrefix+" restart-time "+ + utils.ConvI64toa(block.RestartTime.ValueInt64())) + } + if !block.StaleRouteTime.IsNull() { + configSet = append(configSet, setPrefix+" stale-routes-time "+ + utils.ConvI64toa(block.StaleRouteTime.ValueInt64())) + } + + return configSet +} + +func (block *bgpBlockBfdLivenessDetection) read(itemTrim string) (err error) { + switch { + case balt.CutPrefixInString(&itemTrim, "authentication algorithm "): + block.AuthenticationAlgorithm = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "authentication key-chain "): + block.AuthenticationKeyChain = types.StringValue(strings.Trim(itemTrim, "\"")) + case itemTrim == "authentication loose-check": + block.AuthenticationLooseCheck = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "detection-time threshold "): + block.DetectionTimeThreshold, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "holddown-interval "): + block.HolddownInterval, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "minimum-interval "): + block.MinimumInterval, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "minimum-receive-interval "): + block.MinimumReceiveInterval, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "multiplier "): + block.Multiplier, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "session-mode "): + block.SessionMode = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "transmit-interval threshold "): + block.TransmitIntervalThreshold, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "transmit-interval minimum-interval "): + block.TransmitIntervalMinimumInterval, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "version "): + block.Version = types.StringValue(itemTrim) + } + + return nil +} + +func (block *bgpBlockFamily) read(itemTrim string) (err error) { + switch { + case balt.CutPrefixInString(&itemTrim, "accepted-prefix-limit "): + if block.AcceptedPrefixLimit == nil { + block.AcceptedPrefixLimit = &bgpBlockFamilyBlockPrefixLimit{} + } + switch { + case balt.CutPrefixInString(&itemTrim, "maximum "): + block.AcceptedPrefixLimit.Maximum, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "teardown idle-timeout "): + if itemTrim == "forever" { + block.AcceptedPrefixLimit.TeardownIdleTimeoutForever = types.BoolValue(true) + } else { + block.AcceptedPrefixLimit.TeardownIdleTimeout, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case balt.CutPrefixInString(&itemTrim, "teardown "): + block.AcceptedPrefixLimit.Teardown, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case balt.CutPrefixInString(&itemTrim, "prefix-limit "): + if block.PrefixLimit == nil { + block.PrefixLimit = &bgpBlockFamilyBlockPrefixLimit{} + } + switch { + case balt.CutPrefixInString(&itemTrim, "maximum "): + block.PrefixLimit.Maximum, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "teardown idle-timeout "): + if itemTrim == "forever" { + block.PrefixLimit.TeardownIdleTimeoutForever = types.BoolValue(true) + } else { + block.PrefixLimit.TeardownIdleTimeout, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + case balt.CutPrefixInString(&itemTrim, "teardown "): + block.PrefixLimit.Teardown, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + } + + return nil +} + +func (block *bgpBlockBgpMultipath) read(itemTrim string) { + switch { + case itemTrim == " allow-protection": + block.AllowProtection = types.BoolValue(true) + case itemTrim == " disable": + block.Disable = types.BoolValue(true) + case itemTrim == " multiple-as": + block.MultipleAS = types.BoolValue(true) + } +} + +func (block *bgpBlockGracefulRestart) read(itemTrim string) (err error) { + switch { + case itemTrim == " "+junos.DisableW: + block.Disable = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, " restart-time "): + block.RestartTime, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, " stale-routes-time "): + block.StaleRouteTime, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/providerfwk/upgradestate_bgp_group.go b/internal/providerfwk/upgradestate_bgp_group.go new file mode 100644 index 00000000..66e98f6a --- /dev/null +++ b/internal/providerfwk/upgradestate_bgp_group.go @@ -0,0 +1,462 @@ +package providerfwk + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (rsc *bgpGroup) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "type": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "accept_remote_nexthop": schema.BoolAttribute{ + Optional: true, + }, + "advertise_external": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "advertise_external_conditional": schema.BoolAttribute{ + Optional: true, + }, + "advertise_inactive": schema.BoolAttribute{ + Optional: true, + }, + "advertise_peer_as": schema.BoolAttribute{ + Optional: true, + }, + "no_advertise_peer_as": schema.BoolAttribute{ + Optional: true, + }, + "as_override": schema.BoolAttribute{ + Optional: true, + }, + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + }, + "authentication_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + }, + "cluster": schema.StringAttribute{ + Optional: true, + }, + "damping": schema.BoolAttribute{ + Optional: true, + }, + "export": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "hold_time": schema.Int64Attribute{ + Optional: true, + }, + "import": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "keep_all": schema.BoolAttribute{ + Optional: true, + }, + "keep_none": schema.BoolAttribute{ + Optional: true, + }, + "local_address": schema.StringAttribute{ + Optional: true, + }, + "local_as": schema.StringAttribute{ + Optional: true, + }, + "local_as_alias": schema.BoolAttribute{ + Optional: true, + }, + "local_as_loops": schema.Int64Attribute{ + Optional: true, + }, + "local_as_no_prepend_global_as": schema.BoolAttribute{ + Optional: true, + }, + "local_as_private": schema.BoolAttribute{ + Optional: true, + }, + "local_interface": schema.StringAttribute{ + Optional: true, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + }, + "log_updown": schema.BoolAttribute{ + Optional: true, + }, + "metric_out": schema.Int64Attribute{ + Optional: true, + }, + "metric_out_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "metric_out_igp_delay_med_update": schema.BoolAttribute{ + Optional: true, + }, + "metric_out_igp_offset": schema.Int64Attribute{ + Optional: true, + }, + "metric_out_minimum_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "metric_out_minimum_igp_offset": schema.Int64Attribute{ + Optional: true, + }, + "mtu_discovery": schema.BoolAttribute{ + Optional: true, + }, + "multihop": schema.BoolAttribute{ + Optional: true, + }, + "out_delay": schema.Int64Attribute{ + Optional: true, + }, + "passive": schema.BoolAttribute{ + Optional: true, + }, + "peer_as": schema.StringAttribute{ + Optional: true, + }, + "preference": schema.Int64Attribute{ + Optional: true, + }, + "remove_private": schema.BoolAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "bfd_liveness_detection": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + }, + "authentication_loose_check": schema.BoolAttribute{ + Optional: true, + }, + "detection_time_threshold": schema.Int64Attribute{ + Optional: true, + }, + "holddown_interval": schema.Int64Attribute{ + Optional: true, + }, + "minimum_interval": schema.Int64Attribute{ + Optional: true, + }, + "minimum_receive_interval": schema.Int64Attribute{ + Optional: true, + }, + "multiplier": schema.Int64Attribute{ + Optional: true, + }, + "session_mode": schema.StringAttribute{ + Optional: true, + }, + "transmit_interval_minimum_interval": schema.Int64Attribute{ + Optional: true, + }, + "transmit_interval_threshold": schema.Int64Attribute{ + Optional: true, + }, + "version": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "bgp_multipath": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "allow_protection": schema.BoolAttribute{ + Optional: true, + }, + "disable": schema.BoolAttribute{ + Optional: true, + }, + "multiple_as": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + "family_evpn": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "family_inet": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "family_inet6": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "graceful_restart": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "disable": schema.BoolAttribute{ + Optional: true, + }, + "restart_time": schema.Int64Attribute{ + Optional: true, + }, + "stale_route_time": schema.Int64Attribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeBgpGroupStateV0toV1, + }, + } +} + +func upgradeBgpGroupStateV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + AcceptRemoteNexthop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemovePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export []types.String `tfsdk:"export"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import []types.String `tfsdk:"import"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + Name types.String `tfsdk:"name"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + Type types.String `tfsdk:"type"` + BfdLivenessDetection []bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipath []bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_evpn"` + FamilyInet []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_inet"` + FamilyInet6 []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_inet6"` + GracefulRestart []bgpBlockGracefulRestart `tfsdk:"graceful_restart"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 bgpGroupData + dataV1.ID = dataV0.ID + dataV1.Name = dataV0.Name + dataV1.RoutingInstance = dataV0.RoutingInstance + dataV1.Type = dataV0.Type + dataV1.AcceptRemoteNexthop = dataV0.AcceptRemoteNexthop + dataV1.AdvertiseExternal = dataV0.AdvertiseExternal + dataV1.AdvertiseExternalConditional = dataV0.AdvertiseExternalConditional + dataV1.AdvertiseInactive = dataV0.AdvertiseInactive + dataV1.AdvertisePeerAS = dataV0.AdvertisePeerAS + dataV1.NoAdvertisePeerAS = dataV0.NoAdvertisePeerAS + dataV1.ASOverride = dataV0.ASOverride + dataV1.AuthenticationAlgorithm = dataV0.AuthenticationAlgorithm + dataV1.AuthenticationKey = dataV0.AuthenticationKey + dataV1.AuthenticationKeyChain = dataV0.AuthenticationKeyChain + dataV1.Cluster = dataV0.Cluster + dataV1.Damping = dataV0.Damping + dataV1.Export = dataV0.Export + dataV1.HoldTime = dataV0.HoldTime + dataV1.Import = dataV0.Import + dataV1.KeepAll = dataV0.KeepAll + dataV1.KeepNone = dataV0.KeepNone + dataV1.LocalAddress = dataV0.LocalAddress + dataV1.LocalAS = dataV0.LocalAS + dataV1.LocalASAlias = dataV0.LocalASAlias + dataV1.LocalASLoops = dataV0.LocalASLoops + dataV1.LocalASNoPrependGlobalAS = dataV0.LocalASNoPrependGlobalAS + dataV1.LocalASPrivate = dataV0.LocalASPrivate + dataV1.LocalInterface = dataV0.LocalInterface + dataV1.LocalPreference = dataV0.LocalPreference + dataV1.LogUpdown = dataV0.LogUpdown + dataV1.MetricOut = dataV0.MetricOut + dataV1.MetricOutIgp = dataV0.MetricOutIgp + dataV1.MetricOutIgpDelayMedUpdate = dataV0.MetricOutIgpDelayMedUpdate + dataV1.MetricOutIgpOffset = dataV0.MetricOutIgpOffset + dataV1.MetricOutMinimumIgp = dataV0.MetricOutMinimumIgp + dataV1.MetricOutMinimumIgpOffset = dataV0.MetricOutMinimumIgpOffset + dataV1.MtuDiscovery = dataV0.MtuDiscovery + dataV1.Multihop = dataV0.Multihop + dataV1.OutDelay = dataV0.OutDelay + dataV1.Passive = dataV0.Passive + dataV1.PeerAS = dataV0.PeerAS + dataV1.Preference = dataV0.Preference + dataV1.RemovePrivate = dataV0.RemovePrivate + if len(dataV0.BfdLivenessDetection) > 0 { + dataV1.BfdLivenessDetection = &dataV0.BfdLivenessDetection[0] + } + if len(dataV0.BgpMultipath) > 0 { + dataV1.BgpMultipath = &dataV0.BgpMultipath[0] + } + for _, blockV0 := range dataV0.FamilyEvpn { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyEvpn = append(dataV1.FamilyEvpn, blockV1) + } + for _, blockV0 := range dataV0.FamilyInet { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyInet = append(dataV1.FamilyInet, blockV1) + } + for _, blockV0 := range dataV0.FamilyInet6 { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyInet6 = append(dataV1.FamilyInet6, blockV1) + } + if len(dataV0.GracefulRestart) > 0 { + dataV1.GracefulRestart = &dataV0.GracefulRestart[0] + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_bgp_group_test.go b/internal/providerfwk/upgradestate_bgp_group_test.go new file mode 100644 index 00000000..2fd290d0 --- /dev/null +++ b/internal/providerfwk/upgradestate_bgp_group_test.go @@ -0,0 +1,145 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosBgpGroupUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_SWITCH") == "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosBgpGroupConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosBgpGroupConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosBgpGroupConfigV0() string { + return ` +resource "junos_routing_options" "testacc_bgpgroup" { + clean_on_destroy = true + autonomous_system { + number = "65001" + } + graceful_restart {} +} +resource "junos_routing_instance" "testacc_bgpgroup" { + name = "testacc_bgpgroup" + as = "65000" +} +resource "junos_policyoptions_policy_statement" "testacc_bgpgroup" { + lifecycle { + create_before_destroy = true + } + name = "testacc_bgpgroup" + then { + action = "accept" + } +} +resource "junos_bgp_group" "testacc_bgpgroup" { + depends_on = [ + junos_routing_options.testacc_bgpgroup + ] + name = "testacc_bgpgroup" + routing_instance = junos_routing_instance.testacc_bgpgroup.name + advertise_inactive = true + advertise_peer_as = true + as_override = true + bgp_multipath {} + cluster = "192.0.2.3" + damping = true + log_updown = true + mtu_discovery = true + remove_private = true + passive = true + hold_time = 30 + keep_none = true + local_as = "65001" + local_as_private = true + local_as_loops = 1 + local_preference = 100 + metric_out = 100 + out_delay = 30 + peer_as = "65002" + preference = 100 + authentication_algorithm = "md5" + local_address = "192.0.2.3" + export = [junos_policyoptions_policy_statement.testacc_bgpgroup.name] + import = [junos_policyoptions_policy_statement.testacc_bgpgroup.name] + bfd_liveness_detection { + detection_time_threshold = 60 + transmit_interval_threshold = 30 + transmit_interval_minimum_interval = 10 + holddown_interval = 10 + minimum_interval = 10 + minimum_receive_interval = 10 + multiplier = 2 + session_mode = "automatic" + } + family_inet { + nlri_type = "unicast" + accepted_prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + } + family_inet { + nlri_type = "multicast" + accepted_prefix_limit { + maximum = 2 + teardown_idle_timeout_forever = true + } + prefix_limit { + maximum = 2 + teardown_idle_timeout_forever = true + } + } + family_inet6 { + nlri_type = "unicast" + accepted_prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + } + family_inet6 { + nlri_type = "multicast" + } + graceful_restart { + disable = true + } +} +` +} diff --git a/internal/providerfwk/upgradestate_bgp_neighbor.go b/internal/providerfwk/upgradestate_bgp_neighbor.go new file mode 100644 index 00000000..4fdbc133 --- /dev/null +++ b/internal/providerfwk/upgradestate_bgp_neighbor.go @@ -0,0 +1,461 @@ +package providerfwk + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (rsc *bgpNeighbor) UpgradeState(_ context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "ip": schema.StringAttribute{ + Required: true, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + "group": schema.StringAttribute{ + Required: true, + }, + "accept_remote_nexthop": schema.BoolAttribute{ + Optional: true, + }, + "advertise_external": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "advertise_external_conditional": schema.BoolAttribute{ + Optional: true, + }, + "advertise_inactive": schema.BoolAttribute{ + Optional: true, + }, + "advertise_peer_as": schema.BoolAttribute{ + Optional: true, + }, + "no_advertise_peer_as": schema.BoolAttribute{ + Optional: true, + }, + "as_override": schema.BoolAttribute{ + Optional: true, + }, + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + }, + "authentication_key": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + }, + "cluster": schema.StringAttribute{ + Optional: true, + }, + "damping": schema.BoolAttribute{ + Optional: true, + }, + "export": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "hold_time": schema.Int64Attribute{ + Optional: true, + }, + "import": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "keep_all": schema.BoolAttribute{ + Optional: true, + }, + "keep_none": schema.BoolAttribute{ + Optional: true, + }, + "local_address": schema.StringAttribute{ + Optional: true, + }, + "local_as": schema.StringAttribute{ + Optional: true, + }, + "local_as_alias": schema.BoolAttribute{ + Optional: true, + }, + "local_as_loops": schema.Int64Attribute{ + Optional: true, + }, + "local_as_no_prepend_global_as": schema.BoolAttribute{ + Optional: true, + }, + "local_as_private": schema.BoolAttribute{ + Optional: true, + }, + "local_interface": schema.StringAttribute{ + Optional: true, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + }, + "log_updown": schema.BoolAttribute{ + Optional: true, + }, + "metric_out": schema.Int64Attribute{ + Optional: true, + }, + "metric_out_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "metric_out_igp_delay_med_update": schema.BoolAttribute{ + Optional: true, + }, + "metric_out_igp_offset": schema.Int64Attribute{ + Optional: true, + }, + "metric_out_minimum_igp": schema.BoolAttribute{ + Optional: true, + Computed: true, + }, + "metric_out_minimum_igp_offset": schema.Int64Attribute{ + Optional: true, + }, + "mtu_discovery": schema.BoolAttribute{ + Optional: true, + }, + "multihop": schema.BoolAttribute{ + Optional: true, + }, + "out_delay": schema.Int64Attribute{ + Optional: true, + }, + "passive": schema.BoolAttribute{ + Optional: true, + }, + "peer_as": schema.StringAttribute{ + Optional: true, + }, + "preference": schema.Int64Attribute{ + Optional: true, + }, + "remove_private": schema.BoolAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "bfd_liveness_detection": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "authentication_algorithm": schema.StringAttribute{ + Optional: true, + }, + "authentication_key_chain": schema.StringAttribute{ + Optional: true, + }, + "authentication_loose_check": schema.BoolAttribute{ + Optional: true, + }, + "detection_time_threshold": schema.Int64Attribute{ + Optional: true, + }, + "holddown_interval": schema.Int64Attribute{ + Optional: true, + }, + "minimum_interval": schema.Int64Attribute{ + Optional: true, + }, + "minimum_receive_interval": schema.Int64Attribute{ + Optional: true, + }, + "multiplier": schema.Int64Attribute{ + Optional: true, + }, + "session_mode": schema.StringAttribute{ + Optional: true, + }, + "transmit_interval_minimum_interval": schema.Int64Attribute{ + Optional: true, + }, + "transmit_interval_threshold": schema.Int64Attribute{ + Optional: true, + }, + "version": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + "bgp_multipath": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "allow_protection": schema.BoolAttribute{ + Optional: true, + }, + "disable": schema.BoolAttribute{ + Optional: true, + }, + "multiple_as": schema.BoolAttribute{ + Optional: true, + }, + }, + }, + }, + "family_evpn": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Optional: true, + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "family_inet": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "family_inet6": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nlri_type": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "accepted_prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + "prefix_limit": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: rsc.schemaFamilyPrefixLimitAttributes(), + }, + }, + }, + }, + }, + "graceful_restart": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "disable": schema.BoolAttribute{ + Optional: true, + }, + "restart_time": schema.Int64Attribute{ + Optional: true, + }, + "stale_route_time": schema.Int64Attribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + StateUpgrader: upgradeBgpNeighborStateV0toV1, + }, + } +} + +func upgradeBgpNeighborStateV0toV1( + ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse, +) { + type modelV0 struct { + AcceptRemoteNexthop types.Bool `tfsdk:"accept_remote_nexthop"` + AdvertiseExternal types.Bool `tfsdk:"advertise_external"` + AdvertiseExternalConditional types.Bool `tfsdk:"advertise_external_conditional"` + AdvertiseInactive types.Bool `tfsdk:"advertise_inactive"` + AdvertisePeerAS types.Bool `tfsdk:"advertise_peer_as"` + NoAdvertisePeerAS types.Bool `tfsdk:"no_advertise_peer_as"` + ASOverride types.Bool `tfsdk:"as_override"` + Damping types.Bool `tfsdk:"damping"` + KeepAll types.Bool `tfsdk:"keep_all"` + KeepNone types.Bool `tfsdk:"keep_none"` + LocalASAlias types.Bool `tfsdk:"local_as_alias"` + LocalASNoPrependGlobalAS types.Bool `tfsdk:"local_as_no_prepend_global_as"` + LocalASPrivate types.Bool `tfsdk:"local_as_private"` + LogUpdown types.Bool `tfsdk:"log_updown"` + MetricOutIgp types.Bool `tfsdk:"metric_out_igp"` + MetricOutIgpDelayMedUpdate types.Bool `tfsdk:"metric_out_igp_delay_med_update"` + MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` + MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` + Multihop types.Bool `tfsdk:"multihop"` + Passive types.Bool `tfsdk:"passive"` + RemovePrivate types.Bool `tfsdk:"remove_private"` + AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` + AuthenticationKey types.String `tfsdk:"authentication_key"` + AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` + Cluster types.String `tfsdk:"cluster"` + Export []types.String `tfsdk:"export"` + Group types.String `tfsdk:"group"` + HoldTime types.Int64 `tfsdk:"hold_time"` + ID types.String `tfsdk:"id"` + Import []types.String `tfsdk:"import"` + IP types.String `tfsdk:"ip"` + LocalAddress types.String `tfsdk:"local_address"` + LocalAS types.String `tfsdk:"local_as"` + LocalASLoops types.Int64 `tfsdk:"local_as_loops"` + LocalInterface types.String `tfsdk:"local_interface"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + MetricOut types.Int64 `tfsdk:"metric_out"` + MetricOutIgpOffset types.Int64 `tfsdk:"metric_out_igp_offset"` + MetricOutMinimumIgpOffset types.Int64 `tfsdk:"metric_out_minimum_igp_offset"` + OutDelay types.Int64 `tfsdk:"out_delay"` + PeerAS types.String `tfsdk:"peer_as"` + Preference types.Int64 `tfsdk:"preference"` + RoutingInstance types.String `tfsdk:"routing_instance"` + BfdLivenessDetection []bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpMultipath []bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` + FamilyEvpn []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_evpn"` + FamilyInet []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_inet"` + FamilyInet6 []struct { + NlriType types.String `tfsdk:"nlri_type"` + AcceptedPrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"accepted_prefix_limit"` + PrefixLimit []bgpBlockFamilyBlockPrefixLimit `tfsdk:"prefix_limit"` + } `tfsdk:"family_inet6"` + GracefulRestart []bgpBlockGracefulRestart `tfsdk:"graceful_restart"` + } + + var dataV0 modelV0 + resp.Diagnostics.Append(req.State.Get(ctx, &dataV0)...) + if resp.Diagnostics.HasError() { + return + } + + var dataV1 bgpNeighborData + dataV1.ID = dataV0.ID + dataV1.IP = dataV0.IP + dataV1.RoutingInstance = dataV0.RoutingInstance + dataV1.Group = dataV0.Group + dataV1.AcceptRemoteNexthop = dataV0.AcceptRemoteNexthop + dataV1.AdvertiseExternal = dataV0.AdvertiseExternal + dataV1.AdvertiseExternalConditional = dataV0.AdvertiseExternalConditional + dataV1.AdvertiseInactive = dataV0.AdvertiseInactive + dataV1.AdvertisePeerAS = dataV0.AdvertisePeerAS + dataV1.NoAdvertisePeerAS = dataV0.NoAdvertisePeerAS + dataV1.ASOverride = dataV0.ASOverride + dataV1.AuthenticationAlgorithm = dataV0.AuthenticationAlgorithm + dataV1.AuthenticationKey = dataV0.AuthenticationKey + dataV1.AuthenticationKeyChain = dataV0.AuthenticationKeyChain + dataV1.Cluster = dataV0.Cluster + dataV1.Damping = dataV0.Damping + dataV1.Export = dataV0.Export + dataV1.HoldTime = dataV0.HoldTime + dataV1.Import = dataV0.Import + dataV1.KeepAll = dataV0.KeepAll + dataV1.KeepNone = dataV0.KeepNone + dataV1.LocalAddress = dataV0.LocalAddress + dataV1.LocalAS = dataV0.LocalAS + dataV1.LocalASAlias = dataV0.LocalASAlias + dataV1.LocalASLoops = dataV0.LocalASLoops + dataV1.LocalASNoPrependGlobalAS = dataV0.LocalASNoPrependGlobalAS + dataV1.LocalASPrivate = dataV0.LocalASPrivate + dataV1.LocalInterface = dataV0.LocalInterface + dataV1.LocalPreference = dataV0.LocalPreference + dataV1.LogUpdown = dataV0.LogUpdown + dataV1.MetricOut = dataV0.MetricOut + dataV1.MetricOutIgp = dataV0.MetricOutIgp + dataV1.MetricOutIgpDelayMedUpdate = dataV0.MetricOutIgpDelayMedUpdate + dataV1.MetricOutIgpOffset = dataV0.MetricOutIgpOffset + dataV1.MetricOutMinimumIgp = dataV0.MetricOutMinimumIgp + dataV1.MetricOutMinimumIgpOffset = dataV0.MetricOutMinimumIgpOffset + dataV1.MtuDiscovery = dataV0.MtuDiscovery + dataV1.Multihop = dataV0.Multihop + dataV1.OutDelay = dataV0.OutDelay + dataV1.Passive = dataV0.Passive + dataV1.PeerAS = dataV0.PeerAS + dataV1.Preference = dataV0.Preference + dataV1.RemovePrivate = dataV0.RemovePrivate + if len(dataV0.BfdLivenessDetection) > 0 { + dataV1.BfdLivenessDetection = &dataV0.BfdLivenessDetection[0] + } + if len(dataV0.BgpMultipath) > 0 { + dataV1.BgpMultipath = &dataV0.BgpMultipath[0] + } + for _, blockV0 := range dataV0.FamilyEvpn { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyEvpn = append(dataV1.FamilyEvpn, blockV1) + } + for _, blockV0 := range dataV0.FamilyInet { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyInet = append(dataV1.FamilyInet, blockV1) + } + for _, blockV0 := range dataV0.FamilyInet6 { + blockV1 := bgpBlockFamily{ + NlriType: blockV0.NlriType, + } + if len(blockV0.AcceptedPrefixLimit) > 0 { + blockV1.AcceptedPrefixLimit = &blockV0.AcceptedPrefixLimit[0] + } + if len(blockV0.PrefixLimit) > 0 { + blockV1.PrefixLimit = &blockV0.PrefixLimit[0] + } + dataV1.FamilyInet6 = append(dataV1.FamilyInet6, blockV1) + } + if len(dataV0.GracefulRestart) > 0 { + dataV1.GracefulRestart = &dataV0.GracefulRestart[0] + } + + resp.Diagnostics.Append(resp.State.Set(ctx, dataV1)...) +} diff --git a/internal/providerfwk/upgradestate_bgp_neighbor_test.go b/internal/providerfwk/upgradestate_bgp_neighbor_test.go new file mode 100644 index 00000000..b6647b55 --- /dev/null +++ b/internal/providerfwk/upgradestate_bgp_neighbor_test.go @@ -0,0 +1,150 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestAccJunosBgpNeighborUpgradeStateV0toV1_basic(t *testing.T) { + if os.Getenv("TESTACC_SWITCH") == "" { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "junos": { + VersionConstraint: "1.33.0", + Source: "jeremmfr/junos", + }, + }, + Config: testAccJunosBgpNeighborConfigV0(), + }, + { + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Config: testAccJunosBgpNeighborConfigV0(), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) + } +} + +func testAccJunosBgpNeighborConfigV0() string { + return ` +resource "junos_routing_options" "testacc_bgpneighbor" { + clean_on_destroy = true + autonomous_system { + number = "65001" + } + graceful_restart {} +} +resource "junos_routing_instance" "testacc_bgpneighbor" { + name = "testacc_bgpneighbor" + as = "65000" +} +resource "junos_policyoptions_policy_statement" "testacc_bgpneighbor" { + lifecycle { + create_before_destroy = true + } + name = "testacc_bgpneighbor" + then { + action = "accept" + } +} +resource "junos_bgp_group" "testacc_bgpneighbor" { + name = "testacc_bgpneighbor" + routing_instance = junos_routing_instance.testacc_bgpneighbor.name +} +resource "junos_bgp_neighbor" "testacc_bgpneighbor" { + depends_on = [ + junos_routing_options.testacc_bgpneighbor + ] + ip = "192.0.2.4" + routing_instance = junos_routing_instance.testacc_bgpneighbor.name + group = junos_bgp_group.testacc_bgpneighbor.name + advertise_inactive = true + advertise_peer_as = true + as_override = true + bgp_multipath {} + cluster = "192.0.2.3" + damping = true + log_updown = true + mtu_discovery = true + remove_private = true + passive = true + hold_time = 30 + keep_all = true + local_as = "65001" + local_as_private = true + local_as_loops = 1 + local_preference = 100 + metric_out = 100 + out_delay = 30 + peer_as = "65002" + preference = 100 + authentication_algorithm = "md5" + local_address = "192.0.2.3" + export = [junos_policyoptions_policy_statement.testacc_bgpneighbor.name] + import = [junos_policyoptions_policy_statement.testacc_bgpneighbor.name] + bfd_liveness_detection { + detection_time_threshold = 60 + transmit_interval_threshold = 30 + transmit_interval_minimum_interval = 10 + holddown_interval = 10 + minimum_interval = 10 + minimum_receive_interval = 10 + multiplier = 2 + session_mode = "automatic" + } + family_inet { + nlri_type = "unicast" + accepted_prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + } + family_inet { + nlri_type = "multicast" + accepted_prefix_limit { + maximum = 2 + teardown_idle_timeout_forever = true + } + prefix_limit { + maximum = 2 + teardown_idle_timeout_forever = true + } + } + family_inet6 { + nlri_type = "unicast" + accepted_prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + prefix_limit { + maximum = 2 + teardown = 50 + teardown_idle_timeout = 30 + } + } + family_inet6 { + nlri_type = "multicast" + } + graceful_restart { + disable = true + } +} +` +} diff --git a/internal/providersdk/func_resource_bgp.go b/internal/providersdk/func_resource_bgp.go deleted file mode 100644 index 0907f5b0..00000000 --- a/internal/providersdk/func_resource_bgp.go +++ /dev/null @@ -1,735 +0,0 @@ -package providersdk - -import ( - "fmt" - "strconv" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - balt "github.com/jeremmfr/go-utils/basicalter" - bchk "github.com/jeremmfr/go-utils/basiccheck" - jdecode "github.com/jeremmfr/junosdecode" -) - -type bgpOptions struct { - acceptRemoteNexthop bool - advertiseExternal bool - advertiseExternalConditional bool - advertiseInactive bool - advertisePeerAs bool - asOverride bool - damping bool - keepAll bool - keepNone bool - localAsPrivate bool - localAsAlias bool - localAsNoPrependGlobalAs bool - logUpdown bool - metricOutIgp bool - metricOutIgpDelayMedUpdate bool - metricOutMinimumIgp bool - mtuDiscovery bool - multihop bool - noAdvertisePeerAs bool - removePrivate bool - passive bool - holdTime int - localAsLoops int - localPreference int - metricOut int - metricOutIgpOffset int - metricOutMinimumIgpOffset int - outDelay int - preference int - authenticationAlgorithm string - authenticationKey string - authenticationKeyChain string - bgpType string // group only - cluster string - localAddress string - localAs string - localInterface string - name string // group parameter for neighbor - ip string // for neighbor only - peerAs string - routingInstance string - exportPolicy []string - importPolicy []string - bfdLivenessDetection []map[string]interface{} - bgpMultipath []map[string]interface{} - familyEvpn []map[string]interface{} - familyInet []map[string]interface{} - familyInet6 []map[string]interface{} - gracefulRestart []map[string]interface{} -} - -func delBgpOpts(d *schema.ResourceData, typebgp string, junSess *junos.Session, -) error { - configSet := make([]string, 0) - delPrefix := junos.DeleteLS - if d.Get("routing_instance").(string) != junos.DefaultW { - delPrefix = junos.DelRoutingInstances + d.Get("routing_instance").(string) + " " - } - switch typebgp { - case "group": - delPrefix += "protocols bgp group " + d.Get("name").(string) + " " - case "neighbor": - delPrefix += "protocols bgp group " + d.Get("group").(string) + - " neighbor " + d.Get("ip").(string) + " " - } - - configSet = append(configSet, - delPrefix+"accept-remote-nexthop", - delPrefix+"advertise-external", - delPrefix+"advertise-inactive", - delPrefix+"advertise-peer-as", - delPrefix+"no-advertise-peer-as", - delPrefix+"as-override", - delPrefix+"authentication-algorithm", - delPrefix+"authentication-key", - delPrefix+"authentication-key-chain", - delPrefix+"bfd-liveness-detection", - delPrefix+"cluster", - delPrefix+"damping", - delPrefix+"export", - delPrefix+"family evpn", - delPrefix+"family inet", - delPrefix+"family inet6", - delPrefix+"graceful-restart", - delPrefix+"hold-time", - delPrefix+"import", - delPrefix+"local-address", - delPrefix+"local-as", - delPrefix+"local-interface", - delPrefix+"local-preference", - delPrefix+"log-updown", - delPrefix+"metric-out", - delPrefix+"mtu-discovery", - delPrefix+"multihop", - delPrefix+"multipath", - delPrefix+"out-delay", - delPrefix+"passive", - delPrefix+"peer-as", - delPrefix+"preference", - delPrefix+"remove-private", - delPrefix+"type", - ) - - return junSess.ConfigSet(configSet) -} - -func setBgpOptsSimple(setPrefix string, d *schema.ResourceData, junSess *junos.Session) error { - configSet := []string{setPrefix} - if d.Get("accept_remote_nexthop").(bool) { - configSet = append(configSet, setPrefix+"accept-remote-nexthop") - } - if d.Get("advertise_external").(bool) { - configSet = append(configSet, setPrefix+"advertise-external") - } - if d.Get("advertise_external_conditional").(bool) { - configSet = append(configSet, setPrefix+"advertise-external conditional") - } - if d.Get("advertise_inactive").(bool) { - configSet = append(configSet, setPrefix+"advertise-inactive") - } - if d.Get("advertise_peer_as").(bool) { - configSet = append(configSet, setPrefix+"advertise-peer-as") - } - if d.Get("as_override").(bool) { - configSet = append(configSet, setPrefix+"as-override") - } - if d.Get("authentication_algorithm").(string) != "" { - configSet = append(configSet, setPrefix+"authentication-algorithm "+d.Get("authentication_algorithm").(string)) - } - if d.Get("authentication_key").(string) != "" { - configSet = append(configSet, setPrefix+"authentication-key "+d.Get("authentication_key").(string)) - } - if d.Get("authentication_key_chain").(string) != "" { - configSet = append(configSet, setPrefix+"authentication-key-chain "+d.Get("authentication_key_chain").(string)) - } - for _, v := range d.Get("bgp_multipath").([]interface{}) { - configSet = append(configSet, setPrefix+"multipath") - if v != nil { - bgpMultipah := v.(map[string]interface{}) - if bgpMultipah["allow_protection"].(bool) { - configSet = append(configSet, setPrefix+"multipath allow-protection") - } - if bgpMultipah["disable"].(bool) { - configSet = append(configSet, setPrefix+"multipath disable") - } - if bgpMultipah["multiple_as"].(bool) { - configSet = append(configSet, setPrefix+"multipath multiple-as") - } - } - } - if v := d.Get("cluster").(string); v != "" { - configSet = append(configSet, setPrefix+"cluster "+v) - } - if d.Get("damping").(bool) { - configSet = append(configSet, setPrefix+"damping") - } - for _, v := range d.Get("export").([]interface{}) { - configSet = append(configSet, setPrefix+"export "+v.(string)) - } - if d.Get("hold_time").(int) != 0 { - configSet = append(configSet, setPrefix+"hold-time "+strconv.Itoa(d.Get("hold_time").(int))) - } - for _, v := range d.Get("import").([]interface{}) { - configSet = append(configSet, setPrefix+"import "+v.(string)) - } - if d.Get("keep_all").(bool) { - configSet = append(configSet, setPrefix+"keep all") - } - if d.Get("keep_none").(bool) { - configSet = append(configSet, setPrefix+"keep none") - } - if d.Get("local_address").(string) != "" { - configSet = append(configSet, setPrefix+"local-address "+d.Get("local_address").(string)) - } - if d.Get("local_as").(string) != "" { - configSet = append(configSet, setPrefix+"local-as "+d.Get("local_as").(string)) - } - if d.Get("local_as_alias").(bool) { - configSet = append(configSet, setPrefix+"local-as alias") - } - if d.Get("local_as_loops").(int) != 0 { - configSet = append(configSet, setPrefix+"local-as loops "+strconv.Itoa(d.Get("local_as_loops").(int))) - } - if d.Get("local_as_no_prepend_global_as").(bool) { - configSet = append(configSet, setPrefix+"local-as no-prepend-global-as") - } - if d.Get("local_as_private").(bool) { - configSet = append(configSet, setPrefix+"local-as private") - } - if d.Get("local_interface").(string) != "" { - configSet = append(configSet, setPrefix+"local-interface "+d.Get("local_interface").(string)) - } - if d.Get("local_preference").(int) != -1 { - configSet = append(configSet, setPrefix+"local-preference "+strconv.Itoa(d.Get("local_preference").(int))) - } - if d.Get("log_updown").(bool) { - configSet = append(configSet, setPrefix+"log-updown") - } - if d.Get("metric_out").(int) != -1 { - configSet = append(configSet, setPrefix+"metric-out "+strconv.Itoa(d.Get("metric_out").(int))) - } - if d.Get("metric_out_igp").(bool) { - configSet = append(configSet, setPrefix+"metric-out igp") - } - if d.Get("metric_out_igp_delay_med_update").(bool) { - tfErr := d.Set("metric_out_igp", true) - if tfErr != nil { - panic(tfErr) - } - configSet = append(configSet, setPrefix+"metric-out igp delay-med-update") - } - if d.Get("metric_out_igp_offset").(int) != 0 { - tfErr := d.Set("metric_out_igp", true) - if tfErr != nil { - panic(tfErr) - } - configSet = append(configSet, setPrefix+"metric-out igp "+strconv.Itoa(d.Get("metric_out_igp_offset").(int))) - } - if d.Get("metric_out_minimum_igp").(bool) { - configSet = append(configSet, setPrefix+"metric-out minimum-igp") - } - if d.Get("metric_out_minimum_igp_offset").(int) != 0 { - tfErr := d.Set("metric_out_minimum_igp", true) - if tfErr != nil { - panic(tfErr) - } - configSet = append(configSet, setPrefix+"metric-out minimum-igp "+ - strconv.Itoa(d.Get("metric_out_minimum_igp_offset").(int))) - } - if d.Get("mtu_discovery").(bool) { - configSet = append(configSet, setPrefix+"mtu-discovery") - } - if d.Get("multihop").(bool) { - configSet = append(configSet, setPrefix+"multihop") - } - if d.Get("no_advertise_peer_as").(bool) { - configSet = append(configSet, setPrefix+"no-advertise-peer-as") - } - if d.Get("out_delay").(int) != 0 { - configSet = append(configSet, setPrefix+"out-delay "+strconv.Itoa(d.Get("out_delay").(int))) - } - if d.Get("passive").(bool) { - configSet = append(configSet, setPrefix+"passive") - } - if d.Get("peer_as").(string) != "" { - configSet = append(configSet, setPrefix+"peer-as "+d.Get("peer_as").(string)) - } - if d.Get("preference").(int) != -1 { - configSet = append(configSet, setPrefix+"preference "+strconv.Itoa(d.Get("preference").(int))) - } - if d.Get("remove_private").(bool) { - configSet = append(configSet, setPrefix+"remove-private") - } - - return junSess.ConfigSet(configSet) -} - -func (confRead *bgpOptions) readBgpOptsSimple(itemTrim string) (err error) { - switch { - case itemTrim == "accept-remote-nexthop": - confRead.acceptRemoteNexthop = true - case itemTrim == "advertise-external": - confRead.advertiseExternal = true - case itemTrim == "advertise-external conditional": - confRead.advertiseExternalConditional = true - case itemTrim == "advertise-inactive": - confRead.advertiseInactive = true - case itemTrim == "advertise-peer-as": - confRead.advertisePeerAs = true - case itemTrim == "as-override": - confRead.asOverride = true - case balt.CutPrefixInString(&itemTrim, "authentication-algorithm "): - confRead.authenticationAlgorithm = itemTrim - case balt.CutPrefixInString(&itemTrim, "authentication-key "): - confRead.authenticationKey, err = jdecode.Decode(strings.Trim(itemTrim, "\"")) - if err != nil { - return fmt.Errorf("decoding authentication-key: %w", err) - } - case balt.CutPrefixInString(&itemTrim, "authentication-key-chain "): - confRead.authenticationKeyChain = itemTrim - case balt.CutPrefixInString(&itemTrim, "cluster "): - confRead.cluster = itemTrim - case itemTrim == "damping": - confRead.damping = true - case balt.CutPrefixInString(&itemTrim, "export "): - confRead.exportPolicy = append(confRead.exportPolicy, itemTrim) - case balt.CutPrefixInString(&itemTrim, "hold-time "): - confRead.holdTime, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "import "): - confRead.importPolicy = append(confRead.importPolicy, itemTrim) - case itemTrim == "keep all": - confRead.keepAll = true - case itemTrim == "keep none": - confRead.keepNone = true - case balt.CutPrefixInString(&itemTrim, "local-address "): - confRead.localAddress = itemTrim - case balt.CutPrefixInString(&itemTrim, "local-as "): - switch { - case itemTrim == "private": - confRead.localAsPrivate = true - case itemTrim == "alias": - confRead.localAsAlias = true - case itemTrim == "no-prepend-global-as": - confRead.localAsNoPrependGlobalAs = true - case balt.CutPrefixInString(&itemTrim, "loops "): - confRead.localAsLoops, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - default: - confRead.localAs = itemTrim - } - case balt.CutPrefixInString(&itemTrim, "local-interface "): - confRead.localInterface = itemTrim - case balt.CutPrefixInString(&itemTrim, "local-preference "): - confRead.localPreference, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case itemTrim == "log-updown": - confRead.logUpdown = true - case balt.CutPrefixInString(&itemTrim, "metric-out "): - switch { - case balt.CutPrefixInString(&itemTrim, "igp"): - confRead.metricOutIgp = true - switch { - case itemTrim == " delay-med-update": - confRead.metricOutIgpDelayMedUpdate = true - case balt.CutPrefixInString(&itemTrim, " "): - confRead.metricOutIgpOffset, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - case balt.CutPrefixInString(&itemTrim, "minimum-igp"): - confRead.metricOutMinimumIgp = true - if balt.CutPrefixInString(&itemTrim, " ") { - confRead.metricOutMinimumIgpOffset, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - default: - confRead.metricOut, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - case itemTrim == "mtu-discovery": - confRead.mtuDiscovery = true - case itemTrim == "multihop": - confRead.multihop = true - case balt.CutPrefixInString(&itemTrim, "multipath"): - if len(confRead.bgpMultipath) == 0 { - confRead.bgpMultipath = append(confRead.bgpMultipath, map[string]interface{}{ - "allow_protection": false, - "disable": false, - "multiple_as": false, - }) - } - switch { - case itemTrim == " allow-protection": - confRead.bgpMultipath[0]["allow_protection"] = true - case itemTrim == " disable": - confRead.bgpMultipath[0]["disable"] = true - case itemTrim == " multiple-as": - confRead.bgpMultipath[0]["multiple_as"] = true - } - case itemTrim == "no-advertise-peer-as": - confRead.noAdvertisePeerAs = true - case balt.CutPrefixInString(&itemTrim, "out-delay "): - confRead.outDelay, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case itemTrim == "passive": - confRead.passive = true - case balt.CutPrefixInString(&itemTrim, "peer-as "): - confRead.peerAs = itemTrim - case balt.CutPrefixInString(&itemTrim, "preference "): - confRead.preference, err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case itemTrim == "remove-private": - confRead.removePrivate = true - case balt.CutPrefixInString(&itemTrim, "type "): - confRead.bgpType = itemTrim - } - - return nil -} - -func setBgpOptsBfd(setPrefix string, bfdLivenessDetection []interface{}, junSess *junos.Session, -) error { - configSet := make([]string, 0) - - setPrefixBfd := setPrefix + "bfd-liveness-detection " - for _, v := range bfdLivenessDetection { - if v != nil { - bfdLD := v.(map[string]interface{}) - if bfdLD["authentication_algorithm"].(string) != "" { - configSet = append(configSet, setPrefixBfd+"authentication algorithm "+bfdLD["authentication_algorithm"].(string)) - } - if bfdLD["authentication_key_chain"].(string) != "" { - configSet = append(configSet, setPrefixBfd+"authentication key-chain "+bfdLD["authentication_key_chain"].(string)) - } - if bfdLD["authentication_loose_check"].(bool) { - configSet = append(configSet, setPrefixBfd+"authentication loose-check") - } - if bfdLD["detection_time_threshold"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"detection-time threshold "+ - strconv.Itoa(bfdLD["detection_time_threshold"].(int))) - } - if bfdLD["holddown_interval"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"holddown-interval "+ - strconv.Itoa(bfdLD["holddown_interval"].(int))) - } - if bfdLD["minimum_interval"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"minimum-interval "+ - strconv.Itoa(bfdLD["minimum_interval"].(int))) - } - if bfdLD["minimum_receive_interval"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"minimum-receive-interval "+ - strconv.Itoa(bfdLD["minimum_receive_interval"].(int))) - } - if bfdLD["multiplier"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"multiplier "+ - strconv.Itoa(bfdLD["multiplier"].(int))) - } - if bfdLD["session_mode"].(string) != "" { - configSet = append(configSet, setPrefixBfd+"session-mode "+bfdLD["session_mode"].(string)) - } - if bfdLD["transmit_interval_minimum_interval"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"transmit-interval minimum-interval "+ - strconv.Itoa(bfdLD["transmit_interval_minimum_interval"].(int))) - } - if bfdLD["transmit_interval_threshold"].(int) != 0 { - configSet = append(configSet, setPrefixBfd+"transmit-interval threshold "+ - strconv.Itoa(bfdLD["transmit_interval_threshold"].(int))) - } - if bfdLD["version"].(string) != "" { - configSet = append(configSet, setPrefixBfd+"version "+bfdLD["version"].(string)) - } - } - } - if len(configSet) > 0 { - err := junSess.ConfigSet(configSet) - if err != nil { - return err - } - } - - return nil -} - -func readBgpOptsBfd(itemTrim string, bfdRead map[string]interface{}) (err error) { - switch { - case balt.CutPrefixInString(&itemTrim, "authentication algorithm "): - bfdRead["authentication_algorithm"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "authentication key-chain "): - bfdRead["authentication_key_chain"] = itemTrim - case itemTrim == "authentication loose-check": - bfdRead["authentication_loose_check"] = true - case balt.CutPrefixInString(&itemTrim, "detection-time threshold "): - bfdRead["detection_time_threshold"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "holddown-interval "): - bfdRead["holddown_interval"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "minimum-interval "): - bfdRead["minimum_interval"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "minimum-receive-interval "): - bfdRead["minimum_receive_interval"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "multiplier "): - bfdRead["multiplier"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "session-mode "): - bfdRead["session_mode"] = itemTrim - case balt.CutPrefixInString(&itemTrim, "transmit-interval threshold "): - bfdRead["transmit_interval_threshold"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "transmit-interval minimum-interval "): - bfdRead["transmit_interval_minimum_interval"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "version "): - bfdRead["version"] = itemTrim - } - - return nil -} - -func setBgpOptsFamily( - setPrefix, familyType string, familyOptsList []interface{}, junSess *junos.Session, -) error { - configSet := make([]string, 0) - setPrefixFamily := setPrefix + "family " - switch familyType { - case junos.EvpnW: - setPrefixFamily += "evpn " - case junos.InetW: - setPrefixFamily += "inet " - case junos.Inet6W: - setPrefixFamily += "inet6 " - } - familyNlriTypeList := make([]string, 0) - for _, familyOpts := range familyOptsList { - familyOptsM := familyOpts.(map[string]interface{}) - if bchk.InSlice(familyOptsM["nlri_type"].(string), familyNlriTypeList) { - switch familyType { - case junos.EvpnW: - return fmt.Errorf("multiple blocks family_evpn with the same nlri_type %s", familyOptsM["nlri_type"].(string)) - case junos.InetW: - return fmt.Errorf("multiple blocks family_inet with the same nlri_type %s", familyOptsM["nlri_type"].(string)) - case junos.Inet6W: - return fmt.Errorf("multiple blocks family_inet6 with the same nlri_type %s", familyOptsM["nlri_type"].(string)) - } - } - familyNlriTypeList = append(familyNlriTypeList, familyOptsM["nlri_type"].(string)) - configSet = append(configSet, setPrefixFamily+familyOptsM["nlri_type"].(string)) - for _, v := range familyOptsM["accepted_prefix_limit"].([]interface{}) { - mAccPrefixLimit := v.(map[string]interface{}) - setP := setPrefixFamily + familyOptsM["nlri_type"].(string) + " accepted-prefix-limit " - configSetBgpOptsFamilyPrefixLimit, err := setBgpOptsFamilyPrefixLimit(setP, mAccPrefixLimit) - if err != nil { - return err - } - configSet = append(configSet, configSetBgpOptsFamilyPrefixLimit...) - } - for _, v := range familyOptsM["prefix_limit"].([]interface{}) { - mPrefixLimit := v.(map[string]interface{}) - setP := setPrefixFamily + familyOptsM["nlri_type"].(string) + " prefix-limit " - configSetBgpOptsFamilyPrefixLimit, err := setBgpOptsFamilyPrefixLimit(setP, mPrefixLimit) - if err != nil { - return err - } - configSet = append(configSet, configSetBgpOptsFamilyPrefixLimit...) - } - } - if len(configSet) > 0 { - err := junSess.ConfigSet(configSet) - if err != nil { - return err - } - } - - return nil -} - -func setBgpOptsFamilyPrefixLimit(setPrefix string, prefixLimit map[string]interface{}) ([]string, error) { - configSet := make([]string, 0) - if prefixLimit["maximum"].(int) != 0 { - configSet = append(configSet, setPrefix+"maximum "+strconv.Itoa(prefixLimit["maximum"].(int))) - } - if prefixLimit["teardown"].(int) != 0 { - configSet = append(configSet, setPrefix+"teardown "+strconv.Itoa(prefixLimit["teardown"].(int))) - } - if prefixLimit["teardown_idle_timeout"].(int) != 0 { - if prefixLimit["teardown_idle_timeout_forever"].(bool) { - return configSet, fmt.Errorf("conflict between teardown_idle_timeout and teardown_idle_timeout_forever") - } - configSet = append(configSet, setPrefix+"teardown idle-timeout "+ - strconv.Itoa(prefixLimit["teardown_idle_timeout"].(int))) - } - if prefixLimit["teardown_idle_timeout_forever"].(bool) { - configSet = append(configSet, setPrefix+"teardown idle-timeout forever") - } - - return configSet, nil -} - -func readBgpOptsFamily(itemTrim string, opts []map[string]interface{}) (_ []map[string]interface{}, err error) { - itemTrimFields := strings.Split(itemTrim, " ") - readOpts := map[string]interface{}{ - "nlri_type": itemTrimFields[0], - "accepted_prefix_limit": make([]map[string]interface{}, 0), - "prefix_limit": make([]map[string]interface{}, 0), - } - opts = copyAndRemoveItemMapList("nlri_type", readOpts, opts) - balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") - switch { - case balt.CutPrefixInString(&itemTrim, "accepted-prefix-limit "): - if len(readOpts["accepted_prefix_limit"].([]map[string]interface{})) == 0 { - readOpts["accepted_prefix_limit"] = append(readOpts["accepted_prefix_limit"].([]map[string]interface{}), - map[string]interface{}{ - "maximum": 0, - "teardown": 0, - "teardown_idle_timeout": 0, - "teardown_idle_timeout_forever": false, - }) - } - readOptsPL := readOpts["accepted_prefix_limit"].([]map[string]interface{})[0] - switch { - case balt.CutPrefixInString(&itemTrim, "maximum "): - readOptsPL["maximum"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - - case balt.CutPrefixInString(&itemTrim, "teardown idle-timeout "): - if itemTrim == "forever" { - readOptsPL["teardown_idle_timeout_forever"] = true - } else { - readOptsPL["teardown_idle_timeout"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - case balt.CutPrefixInString(&itemTrim, "teardown "): - readOptsPL["teardown"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - case balt.CutPrefixInString(&itemTrim, "prefix-limit "): - if len(readOpts["prefix_limit"].([]map[string]interface{})) == 0 { - readOpts["prefix_limit"] = append(readOpts["prefix_limit"].([]map[string]interface{}), - map[string]interface{}{ - "maximum": 0, - "teardown": 0, - "teardown_idle_timeout": 0, - "teardown_idle_timeout_forever": false, - }) - } - readOptsPL := readOpts["prefix_limit"].([]map[string]interface{})[0] - switch { - case balt.CutPrefixInString(&itemTrim, "maximum "): - readOptsPL["maximum"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "teardown idle-timeout "): - if itemTrim == "forever" { - readOptsPL["teardown_idle_timeout_forever"] = true - } else { - readOptsPL["teardown_idle_timeout"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - case balt.CutPrefixInString(&itemTrim, "teardown "): - readOptsPL["teardown"], err = strconv.Atoi(itemTrim) - if err != nil { - return append(opts, readOpts), fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - } - - return append(opts, readOpts), nil -} - -func setBgpOptsGrafefulRestart( - setPrefix string, gracefulRestarts []interface{}, junSess *junos.Session, -) error { - configSet := make([]string, 0) - - for _, v := range gracefulRestarts { - if v != nil { - gRestart := v.(map[string]interface{}) - if gRestart["disable"].(bool) { - configSet = append(configSet, setPrefix+"graceful-restart disable") - } - if gRestart["restart_time"].(int) != 0 { - configSet = append(configSet, setPrefix+"graceful-restart restart-time "+ - strconv.Itoa(gRestart["restart_time"].(int))) - } - if gRestart["stale_route_time"].(int) != 0 { - configSet = append(configSet, setPrefix+"graceful-restart stale-routes-time "+ - strconv.Itoa(gRestart["stale_route_time"].(int))) - } - } - } - if len(configSet) > 0 { - err := junSess.ConfigSet(configSet) - if err != nil { - return err - } - } - - return nil -} - -func readBgpOptsGracefulRestart(itemTrim string, grRead map[string]interface{}) (err error) { - switch { - case itemTrim == junos.DisableW: - grRead["disable"] = true - case balt.CutPrefixInString(&itemTrim, "restart-time "): - grRead["restart_time"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - case balt.CutPrefixInString(&itemTrim, "stale-routes-time "): - grRead["stale_route_time"], err = strconv.Atoi(itemTrim) - if err != nil { - return fmt.Errorf(failedConvAtoiError, itemTrim, err) - } - } - - return nil -} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index 8d354b39..c2738974 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -149,8 +149,6 @@ func Provider() *schema.Provider { "junos_aggregate_route": resourceAggregateRoute(), "junos_application": resourceApplication(), "junos_application_set": resourceApplicationSet(), - "junos_bgp_group": resourceBgpGroup(), - "junos_bgp_neighbor": resourceBgpNeighbor(), "junos_bridge_domain": resourceBridgeDomain(), "junos_chassis_cluster": resourceChassisCluster(), "junos_chassis_redundancy": resourceChassisRedundancy(), diff --git a/internal/providersdk/resource_bgp_group.go b/internal/providersdk/resource_bgp_group.go deleted file mode 100644 index 885c7fec..00000000 --- a/internal/providersdk/resource_bgp_group.go +++ /dev/null @@ -1,1120 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - balt "github.com/jeremmfr/go-utils/basicalter" -) - -func resourceBgpGroup() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceBgpGroupCreate, - ReadWithoutTimeout: resourceBgpGroupRead, - UpdateWithoutTimeout: resourceBgpGroupUpdate, - DeleteWithoutTimeout: resourceBgpGroupDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceBgpGroupImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: junos.DefaultW, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "type": { - Type: schema.TypeString, - Optional: true, - Default: "external", - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"internal", "external"}, false), - }, - "accept_remote_nexthop": { - Type: schema.TypeBool, - Optional: true, - }, - "advertise_external": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_external_conditional"}, - }, - "advertise_external_conditional": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_external"}, - }, - "advertise_inactive": { - Type: schema.TypeBool, - Optional: true, - }, - "advertise_peer_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_advertise_peer_as"}, - }, - "no_advertise_peer_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_peer_as"}, - }, - "as_override": { - Type: schema.TypeBool, - Optional: true, - }, - "authentication_algorithm": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"authentication_key"}, - }, - "authentication_key": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - ConflictsWith: []string{"authentication_algorithm", "authentication_key_chain"}, - }, - "authentication_key_chain": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"authentication_key"}, - }, - "bfd_liveness_detection": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "authentication_algorithm": { - Type: schema.TypeString, - Optional: true, - }, - "authentication_key_chain": { - Type: schema.TypeString, - Optional: true, - }, - "authentication_loose_check": { - Type: schema.TypeBool, - Optional: true, - }, - "detection_time_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "holddown_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "minimum_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "minimum_receive_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "multiplier": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255), - }, - "session_mode": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice( - []string{"automatic", "multihop", "single-hop"}, false), - }, - "transmit_interval_minimum_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "transmit_interval_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "version": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "bgp_multipath": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "allow_protection": { - Type: schema.TypeBool, - Optional: true, - }, - "disable": { - Type: schema.TypeBool, - Optional: true, - }, - "multiple_as": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "cluster": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsIPAddress, - }, - "damping": { - Type: schema.TypeBool, - Optional: true, - }, - "export": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "family_evpn": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Optional: true, - Default: "signaling", - ValidateFunc: validation.StringInSlice([]string{"signaling"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "family_inet": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{"any", "flow", "labeled-unicast", "unicast", "multicast"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "family_inet6": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{"any", "flow", "labeled-unicast", "unicast", "multicast"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "graceful_restart": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "disable": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{ - "graceful_restart.0.restart_time", - "graceful_restart.0.stale_route_time", - }, - }, - "restart_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 600), - ConflictsWith: []string{"graceful_restart.0.disable"}, - }, - "stale_route_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 600), - ConflictsWith: []string{"graceful_restart.0.disable"}, - }, - }, - }, - }, - "hold_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(3, 65535), - }, - "import": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "keep_all": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"keep_none"}, - }, - "keep_none": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"keep_all"}, - }, - "local_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsIPAddress, - }, - "local_as": { - Type: schema.TypeString, - Optional: true, - }, - "local_as_alias": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_private", "local_as_no_prepend_global_as"}, - }, - "local_as_loops": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 10), - }, - "local_as_no_prepend_global_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_private", "local_as_alias"}, - }, - "local_as_private": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_alias", "local_as_no_prepend_global_as"}, - }, - "local_interface": { - Type: schema.TypeString, - Optional: true, - }, - "local_preference": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - }, - "log_updown": { - Type: schema.TypeBool, - Optional: true, - }, - "metric_out": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - ConflictsWith: []string{ - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp_delay_med_update": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp_offset": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(-2147483648, 2147483647), - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_minimum_igp": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - }, - }, - "metric_out_minimum_igp_offset": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(-2147483648, 2147483647), - ConflictsWith: []string{ - "metric_out", - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - }, - }, - "mtu_discovery": { - Type: schema.TypeBool, - Optional: true, - }, - "multihop": { - Type: schema.TypeBool, - Optional: true, - }, - "out_delay": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "passive": { - Type: schema.TypeBool, - Optional: true, - }, - "peer_as": { - Type: schema.TypeString, - Optional: true, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - }, - "remove_private": { - Type: schema.TypeBool, - Optional: true, - }, - }, - } -} - -func resourceBgpGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setBgpGroup(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("name").(string) + junos.IDSeparator + d.Get("routing_instance").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if d.Get("routing_instance").(string) != junos.DefaultW { - instanceExists, err := checkRoutingInstanceExists(d.Get("routing_instance").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if !instanceExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("routing instance %v doesn't exist", d.Get("routing_instance").(string)))...) - } - } - bgpGroupxists, err := checkBgpGroupExists(d.Get("name").(string), d.Get("routing_instance").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if bgpGroupxists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("bgp group %v already exists in routing-instance %v", - d.Get("name").(string), d.Get("routing_instance").(string)))...) - } - if err := setBgpGroup(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_bgp_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - bgpGroupxists, err = checkBgpGroupExists(d.Get("name").(string), d.Get("routing_instance").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if bgpGroupxists { - d.SetId(d.Get("name").(string) + junos.IDSeparator + d.Get("routing_instance").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("bgp group %v not exists in routing-instance %v after commit "+ - "=> check your config", d.Get("name").(string), d.Get("routing_instance").(string)))...) - } - - return append(diagWarns, resourceBgpGroupReadWJunSess(d, junSess)...) -} - -func resourceBgpGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourceBgpGroupReadWJunSess(d, junSess) -} - -func resourceBgpGroupReadWJunSess(d *schema.ResourceData, junSess *junos.Session) diag.Diagnostics { - junos.MutexLock() - bgpGroupOptions, err := readBgpGroup(d.Get("name").(string), d.Get("routing_instance").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if bgpGroupOptions.name == "" { - d.SetId("") - } else { - fillBgpGroupData(d, bgpGroupOptions) - } - - return nil -} - -func resourceBgpGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delBgpOpts(d, "group", junSess); err != nil { - return diag.FromErr(err) - } - if err := setBgpGroup(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delBgpOpts(d, "group", junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setBgpGroup(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_bgp_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceBgpGroupReadWJunSess(d, junSess)...) -} - -func resourceBgpGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delBgpGroup(d, junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delBgpGroup(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_bgp_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceBgpGroupImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - idSplit := strings.Split(d.Id(), junos.IDSeparator) - if len(idSplit) < 2 { - return nil, fmt.Errorf("missing element(s) in id with separator %v", junos.IDSeparator) - } - bgpGroupxists, err := checkBgpGroupExists(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - if !bgpGroupxists { - return nil, fmt.Errorf("don't find bgp group with id '%v' "+ - "(id must be "+junos.IDSeparator+")", d.Id()) - } - bgpGroupOptions, err := readBgpGroup(idSplit[0], idSplit[1], junSess) - if err != nil { - return nil, err - } - fillBgpGroupData(d, bgpGroupOptions) - result[0] = d - - return result, nil -} - -func checkBgpGroupExists(bgpGroup, instance string, junSess *junos.Session) (_ bool, err error) { - var showConfig string - if instance == junos.DefaultW { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "protocols bgp group " + bgpGroup + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "protocols bgp group " + bgpGroup + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setBgpGroup(d *schema.ResourceData, junSess *junos.Session) error { - setPrefix := junos.SetLS - if d.Get("routing_instance").(string) != junos.DefaultW { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + " " - } - setPrefix += "protocols bgp group " + d.Get("name").(string) + " " - - if err := junSess.ConfigSet([]string{setPrefix + "type " + d.Get("type").(string)}); err != nil { - return err - } - if d.Get("type").(string) == "external" { - if d.Get("advertise_external").(bool) { - return fmt.Errorf("conflict between type=external and advertise_external") - } - if d.Get("accept_remote_nexthop").(bool) && d.Get("multihop").(bool) { - return fmt.Errorf("conflict between type=external and accept_remote_nexthop + multihop") - } - } - if err := setBgpOptsSimple(setPrefix, d, junSess); err != nil { - return err - } - if err := setBgpOptsBfd(setPrefix, d.Get("bfd_liveness_detection").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, "evpn", d.Get("family_evpn").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, junos.InetW, d.Get("family_inet").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, junos.Inet6W, d.Get("family_inet6").([]interface{}), junSess); err != nil { - return err - } - - return setBgpOptsGrafefulRestart(setPrefix, d.Get("graceful_restart").([]interface{}), junSess) -} - -func readBgpGroup(bgpGroup, instance string, junSess *junos.Session, -) (confRead bgpOptions, err error) { - // default -1 - confRead.localPreference = -1 - confRead.metricOut = -1 - confRead.preference = -1 - var showConfig string - if instance == junos.DefaultW { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "protocols bgp group " + bgpGroup + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "protocols bgp group " + bgpGroup + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - } - if showConfig != junos.EmptyW { - confRead.name = bgpGroup - confRead.routingInstance = instance - for _, item := range strings.Split(showConfig, "\n") { - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - itemTrim := strings.TrimPrefix(item, junos.SetLS) - switch { - case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): - if len(confRead.bfdLivenessDetection) == 0 { - confRead.bfdLivenessDetection = append(confRead.bfdLivenessDetection, - map[string]interface{}{ - "authentication_algorithm": "", - "authentication_key_chain": "", - "authentication_loose_check": false, - "detection_time_threshold": 0, - "holddown_interval": 0, - "minimum_interval": 0, - "minimum_receive_interval": 0, - "multiplier": 0, - "session_mode": "", - "transmit_interval_minimum_interval": 0, - "transmit_interval_threshold": 0, - "version": "", - }) - } - if err := readBgpOptsBfd(itemTrim, confRead.bfdLivenessDetection[0]); err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "family evpn "): - confRead.familyEvpn, err = readBgpOptsFamily(itemTrim, confRead.familyEvpn) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "family inet "): - confRead.familyInet, err = readBgpOptsFamily(itemTrim, confRead.familyInet) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "family inet6 "): - confRead.familyInet6, err = readBgpOptsFamily(itemTrim, confRead.familyInet6) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "graceful-restart "): - if len(confRead.gracefulRestart) == 0 { - confRead.gracefulRestart = append(confRead.gracefulRestart, map[string]interface{}{ - "disable": false, - "restart_time": 0, - "stale_route_time": 0, - }) - } - if err := readBgpOptsGracefulRestart(itemTrim, confRead.gracefulRestart[0]); err != nil { - return confRead, err - } - default: - err = confRead.readBgpOptsSimple(itemTrim) - if err != nil { - return confRead, err - } - } - } - } - - return confRead, nil -} - -func delBgpGroup(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - if d.Get("routing_instance").(string) == junos.DefaultW { - configSet = append(configSet, "delete protocols bgp group "+d.Get("name").(string)) - } else { - configSet = append(configSet, junos.DelRoutingInstances+d.Get("routing_instance").(string)+ - " protocols bgp group "+d.Get("name").(string)) - } - - return junSess.ConfigSet(configSet) -} - -func fillBgpGroupData(d *schema.ResourceData, bgpGroupOptions bgpOptions) { - if tfErr := d.Set("name", bgpGroupOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", bgpGroupOptions.routingInstance); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("accept_remote_nexthop", bgpGroupOptions.acceptRemoteNexthop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_external", bgpGroupOptions.advertiseExternal); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_external_conditional", bgpGroupOptions.advertiseExternalConditional); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_inactive", bgpGroupOptions.advertiseInactive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_peer_as", bgpGroupOptions.advertisePeerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_override", bgpGroupOptions.asOverride); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_algorithm", bgpGroupOptions.authenticationAlgorithm); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_key", bgpGroupOptions.authenticationKey); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_key_chain", bgpGroupOptions.authenticationKeyChain); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("bfd_liveness_detection", bgpGroupOptions.bfdLivenessDetection); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("cluster", bgpGroupOptions.cluster); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("damping", bgpGroupOptions.damping); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("export", bgpGroupOptions.exportPolicy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_evpn", bgpGroupOptions.familyEvpn); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_inet", bgpGroupOptions.familyInet); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_inet6", bgpGroupOptions.familyInet6); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("graceful_restart", bgpGroupOptions.gracefulRestart); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("hold_time", bgpGroupOptions.holdTime); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("import", bgpGroupOptions.importPolicy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("keep_all", bgpGroupOptions.keepAll); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("keep_none", bgpGroupOptions.keepNone); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_address", bgpGroupOptions.localAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as", bgpGroupOptions.localAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_alias", bgpGroupOptions.localAsAlias); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_loops", bgpGroupOptions.localAsLoops); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_no_prepend_global_as", bgpGroupOptions.localAsNoPrependGlobalAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_private", bgpGroupOptions.localAsPrivate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_interface", bgpGroupOptions.localInterface); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_preference", bgpGroupOptions.localPreference); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("log_updown", bgpGroupOptions.logUpdown); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out", bgpGroupOptions.metricOut); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp", bgpGroupOptions.metricOutIgp); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp_delay_med_update", bgpGroupOptions.metricOutIgpDelayMedUpdate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp_offset", bgpGroupOptions.metricOutIgpOffset); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_minimum_igp", bgpGroupOptions.metricOutMinimumIgp); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_minimum_igp_offset", bgpGroupOptions.metricOutMinimumIgpOffset); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("mtu_discovery", bgpGroupOptions.mtuDiscovery); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("multihop", bgpGroupOptions.multihop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("bgp_multipath", bgpGroupOptions.bgpMultipath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_advertise_peer_as", bgpGroupOptions.noAdvertisePeerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("out_delay", bgpGroupOptions.outDelay); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("passive", bgpGroupOptions.passive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("peer_as", bgpGroupOptions.peerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("preference", bgpGroupOptions.preference); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("remove_private", bgpGroupOptions.removePrivate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("type", bgpGroupOptions.bgpType); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/providersdk/resource_bgp_neighbor.go b/internal/providersdk/resource_bgp_neighbor.go deleted file mode 100644 index 25026da0..00000000 --- a/internal/providersdk/resource_bgp_neighbor.go +++ /dev/null @@ -1,1137 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - balt "github.com/jeremmfr/go-utils/basicalter" -) - -func resourceBgpNeighbor() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourceBgpNeighborCreate, - ReadWithoutTimeout: resourceBgpNeighborRead, - UpdateWithoutTimeout: resourceBgpNeighborUpdate, - DeleteWithoutTimeout: resourceBgpNeighborDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourceBgpNeighborImport, - }, - Schema: map[string]*schema.Schema{ - "ip": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateFunc: validation.IsIPAddress, - }, - "routing_instance": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: junos.DefaultW, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "group": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "accept_remote_nexthop": { - Type: schema.TypeBool, - Optional: true, - }, - "advertise_external": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_external_conditional"}, - }, - "advertise_external_conditional": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_external"}, - }, - "advertise_inactive": { - Type: schema.TypeBool, - Optional: true, - }, - "advertise_peer_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"no_advertise_peer_as"}, - }, - "no_advertise_peer_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"advertise_peer_as"}, - }, - "as_override": { - Type: schema.TypeBool, - Optional: true, - }, - "authentication_algorithm": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"authentication_key"}, - }, - "authentication_key": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - ConflictsWith: []string{"authentication_algorithm", "authentication_key_chain"}, - }, - "authentication_key_chain": { - Type: schema.TypeString, - Optional: true, - ConflictsWith: []string{"authentication_key"}, - }, - "bfd_liveness_detection": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "authentication_algorithm": { - Type: schema.TypeString, - Optional: true, - }, - "authentication_key_chain": { - Type: schema.TypeString, - Optional: true, - }, - "authentication_loose_check": { - Type: schema.TypeBool, - Optional: true, - }, - "detection_time_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "holddown_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "minimum_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "minimum_receive_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "multiplier": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255), - }, - "session_mode": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"automatic", "multihop", "single-hop"}, false), - }, - "transmit_interval_minimum_interval": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 255000), - }, - "transmit_interval_threshold": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "version": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, - }, - "bgp_multipath": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "allow_protection": { - Type: schema.TypeBool, - Optional: true, - }, - "disable": { - Type: schema.TypeBool, - Optional: true, - }, - "multiple_as": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "cluster": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsIPAddress, - }, - "damping": { - Type: schema.TypeBool, - Optional: true, - }, - "export": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "family_evpn": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Optional: true, - Default: "signaling", - ValidateFunc: validation.StringInSlice([]string{"signaling"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "family_inet": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{"any", "flow", "labeled-unicast", "unicast", "multicast"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "family_inet6": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "nlri_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice( - []string{"any", "flow", "labeled-unicast", "unicast", "multicast"}, false), - }, - "accepted_prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - "prefix_limit": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "maximum": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(1, 4294967295), - }, - "teardown": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 100), - }, - "teardown_idle_timeout": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 2400), - }, - "teardown_idle_timeout_forever": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, - }, - }, - }, - }, - "graceful_restart": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "disable": { - Type: schema.TypeBool, - Optional: true, - }, - "restart_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 600), - }, - "stale_route_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 600), - }, - }, - }, - }, - "hold_time": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(3, 65535), - }, - "import": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - }, - "keep_all": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"keep_none"}, - }, - "keep_none": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"keep_all"}, - }, - "local_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.IsIPAddress, - }, - "local_as": { - Type: schema.TypeString, - Optional: true, - }, - "local_as_alias": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_private", "local_as_no_prepend_global_as"}, - }, - "local_as_loops": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 10), - }, - "local_as_no_prepend_global_as": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_private", "local_as_alias"}, - }, - "local_as_private": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{"local_as_alias", "local_as_no_prepend_global_as"}, - }, - "local_interface": { - Type: schema.TypeString, - Optional: true, - }, - "local_preference": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - }, - "log_updown": { - Type: schema.TypeBool, - Optional: true, - }, - "metric_out": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - ConflictsWith: []string{ - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp_delay_med_update": { - Type: schema.TypeBool, - Optional: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_igp_offset": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(-2147483648, 2147483647), - ConflictsWith: []string{ - "metric_out", - "metric_out_minimum_igp", - "metric_out_minimum_igp_offset", - }, - }, - "metric_out_minimum_igp": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{ - "metric_out", - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - }, - }, - "metric_out_minimum_igp_offset": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(-2147483648, 2147483647), - ConflictsWith: []string{ - "metric_out", - "metric_out_igp", - "metric_out_igp_offset", - "metric_out_igp_delay_med_update", - }, - }, - "mtu_discovery": { - Type: schema.TypeBool, - Optional: true, - }, - "multihop": { - Type: schema.TypeBool, - Optional: true, - }, - "out_delay": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(1, 65535), - }, - "passive": { - Type: schema.TypeBool, - Optional: true, - }, - "peer_as": { - Type: schema.TypeString, - Optional: true, - }, - "preference": { - Type: schema.TypeInt, - Optional: true, - ValidateFunc: validation.IntBetween(0, 4294967295), - Default: -1, - }, - "remove_private": { - Type: schema.TypeBool, - Optional: true, - }, - }, - } -} - -func resourceBgpNeighborCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setBgpNeighbor(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("ip").(string) + - junos.IDSeparator + d.Get("routing_instance").(string) + - junos.IDSeparator + d.Get("group").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if d.Get("routing_instance").(string) != junos.DefaultW { - instanceExists, err := checkRoutingInstanceExists(d.Get("routing_instance").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if !instanceExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("routing instance %v doesn't exist", d.Get("routing_instance").(string)))...) - } - } - bgpGroupExists, err := checkBgpGroupExists(d.Get("group").(string), d.Get("routing_instance").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if !bgpGroupExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("bgp group %v doesn't exist", d.Get("group").(string)))...) - } - bgpNeighborxists, err := checkBgpNeighborExists( - d.Get("ip").(string), - d.Get("routing_instance").(string), - d.Get("group").(string), - junSess, - ) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if bgpNeighborxists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(fmt.Errorf("bgp neighbor %v already exists in group %v (routing-instance %v)", - d.Get("ip").(string), d.Get("group").(string), d.Get("routing_instance").(string)))...) - } - if err := setBgpNeighbor(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_bgp_neighbor") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - bgpNeighborxists, err = checkBgpNeighborExists( - d.Get("ip").(string), - d.Get("routing_instance").(string), - d.Get("group").(string), - junSess, - ) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if bgpNeighborxists { - d.SetId(d.Get("ip").(string) + - junos.IDSeparator + d.Get("routing_instance").(string) + - junos.IDSeparator + d.Get("group").(string)) - } else { - return append(diagWarns, - diag.FromErr(fmt.Errorf("bgp neighbor %v not exists in group %v (routing-instance %v) after commit "+ - "=> check your config", d.Get("ip").(string), d.Get("group").(string), d.Get("routing_instance").(string)))...) - } - - return append(diagWarns, resourceBgpNeighborReadWJunSess(d, junSess)...) -} - -func resourceBgpNeighborRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourceBgpNeighborReadWJunSess(d, junSess) -} - -func resourceBgpNeighborReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - bgpNeighborOptions, err := readBgpNeighbor( - d.Get("ip").(string), - d.Get("routing_instance").(string), - d.Get("group").(string), - junSess, - ) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if bgpNeighborOptions.ip == "" { - d.SetId("") - } else { - fillBgpNeighborData(d, bgpNeighborOptions) - } - - return nil -} - -func resourceBgpNeighborUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delBgpOpts(d, "neighbor", junSess); err != nil { - return diag.FromErr(err) - } - if err := setBgpNeighbor(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delBgpOpts(d, "neighbor", junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setBgpNeighbor(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_bgp_neighbor") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourceBgpNeighborReadWJunSess(d, junSess)...) -} - -func resourceBgpNeighborDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delBgpNeighbor(d, junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delBgpNeighbor(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_bgp_neighbor") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourceBgpNeighborImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - idSplit := strings.Split(d.Id(), junos.IDSeparator) - if len(idSplit) < 3 { - return nil, fmt.Errorf("missing element(s) in id with separator %v", junos.IDSeparator) - } - bgpNeighborxists, err := checkBgpNeighborExists(idSplit[0], idSplit[1], idSplit[2], junSess) - if err != nil { - return nil, err - } - if !bgpNeighborxists { - return nil, fmt.Errorf("don't find bgp neighbor with id '%v' "+ - "(id must be "+junos.IDSeparator+""+junos.IDSeparator+")", d.Id()) - } - bgpNeighborOptions, err := readBgpNeighbor(idSplit[0], idSplit[1], idSplit[2], junSess) - if err != nil { - return nil, err - } - fillBgpNeighborData(d, bgpNeighborOptions) - result[0] = d - - return result, nil -} - -func checkBgpNeighborExists(ip, instance, group string, junSess *junos.Session) (_ bool, err error) { - var showConfig string - if instance == junos.DefaultW { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "protocols bgp group " + group + " neighbor " + ip + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "protocols bgp group " + group + " neighbor " + ip + junos.PipeDisplaySet) - if err != nil { - return false, err - } - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setBgpNeighbor(d *schema.ResourceData, junSess *junos.Session) error { - setPrefix := junos.SetLS - if d.Get("routing_instance").(string) != junos.DefaultW { - setPrefix = junos.SetRoutingInstances + d.Get("routing_instance").(string) + " " - } - setPrefix += "protocols bgp group " + d.Get("group").(string) + " neighbor " + d.Get("ip").(string) + " " - - if err := setBgpOptsSimple(setPrefix, d, junSess); err != nil { - return err - } - if err := setBgpOptsBfd(setPrefix, d.Get("bfd_liveness_detection").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, "evpn", d.Get("family_evpn").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, junos.InetW, d.Get("family_inet").([]interface{}), junSess); err != nil { - return err - } - if err := setBgpOptsFamily(setPrefix, junos.Inet6W, d.Get("family_inet6").([]interface{}), junSess); err != nil { - return err - } - - return setBgpOptsGrafefulRestart(setPrefix, d.Get("graceful_restart").([]interface{}), junSess) -} - -func readBgpNeighbor(ip, instance, group string, junSess *junos.Session, -) (confRead bgpOptions, err error) { - // default -1 - confRead.localPreference = -1 - confRead.metricOut = -1 - confRead.preference = -1 - var showConfig string - if instance == junos.DefaultW { - showConfig, err = junSess.Command(junos.CmdShowConfig + - "protocols bgp group " + group + " neighbor " + ip + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - } else { - showConfig, err = junSess.Command(junos.CmdShowConfig + junos.RoutingInstancesWS + instance + " " + - "protocols bgp group " + group + " neighbor " + ip + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - } - if showConfig != junos.EmptyW { - confRead.ip = ip - confRead.routingInstance = instance - confRead.name = group - for _, item := range strings.Split(showConfig, "\n") { - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - itemTrim := strings.TrimPrefix(item, junos.SetLS) - switch { - case balt.CutPrefixInString(&itemTrim, "family evpn "): - confRead.familyEvpn, err = readBgpOptsFamily(itemTrim, confRead.familyEvpn) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "family inet "): - confRead.familyInet, err = readBgpOptsFamily(itemTrim, confRead.familyInet) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "family inet6 "): - confRead.familyInet6, err = readBgpOptsFamily(itemTrim, confRead.familyInet6) - if err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): - if len(confRead.bfdLivenessDetection) == 0 { - confRead.bfdLivenessDetection = append(confRead.bfdLivenessDetection, - map[string]interface{}{ - "authentication_algorithm": "", - "authentication_key_chain": "", - "authentication_loose_check": false, - "detection_time_threshold": 0, - "holddown_interval": 0, - "minimum_interval": 0, - "minimum_receive_interval": 0, - "multiplier": 0, - "session_mode": "", - "transmit_interval_minimum_interval": 0, - "transmit_interval_threshold": 0, - "version": "", - }) - } - if err := readBgpOptsBfd(itemTrim, confRead.bfdLivenessDetection[0]); err != nil { - return confRead, err - } - case balt.CutPrefixInString(&itemTrim, "graceful-restart "): - if len(confRead.gracefulRestart) == 0 { - confRead.gracefulRestart = append(confRead.gracefulRestart, map[string]interface{}{ - "disable": false, - "restart_time": 0, - "stale_route_time": 0, - }) - } - if err := readBgpOptsGracefulRestart(itemTrim, confRead.gracefulRestart[0]); err != nil { - return confRead, err - } - default: - err = confRead.readBgpOptsSimple(itemTrim) - if err != nil { - return confRead, err - } - } - } - } - - return confRead, nil -} - -func delBgpNeighbor(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - if d.Get("routing_instance").(string) == junos.DefaultW { - configSet = append(configSet, "delete protocols bgp"+ - " group "+d.Get("group").(string)+ - " neighbor "+d.Get("ip").(string)) - } else { - configSet = append(configSet, junos.DelRoutingInstances+d.Get("routing_instance").(string)+ - " protocols bgp group "+d.Get("group").(string)+ - " neighbor "+d.Get("ip").(string)) - } - - return junSess.ConfigSet(configSet) -} - -func fillBgpNeighborData(d *schema.ResourceData, bgpNeighborOptions bgpOptions) { - if tfErr := d.Set("ip", bgpNeighborOptions.ip); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("routing_instance", bgpNeighborOptions.routingInstance); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("group", bgpNeighborOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("accept_remote_nexthop", bgpNeighborOptions.acceptRemoteNexthop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_external", bgpNeighborOptions.advertiseExternal); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_external_conditional", bgpNeighborOptions.advertiseExternalConditional); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_inactive", bgpNeighborOptions.advertiseInactive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("advertise_peer_as", bgpNeighborOptions.advertisePeerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_override", bgpNeighborOptions.asOverride); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_algorithm", bgpNeighborOptions.authenticationAlgorithm); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_key", bgpNeighborOptions.authenticationKey); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("authentication_key_chain", bgpNeighborOptions.authenticationKeyChain); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("bfd_liveness_detection", bgpNeighborOptions.bfdLivenessDetection); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("cluster", bgpNeighborOptions.cluster); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("damping", bgpNeighborOptions.damping); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("export", bgpNeighborOptions.exportPolicy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_evpn", bgpNeighborOptions.familyEvpn); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_inet", bgpNeighborOptions.familyInet); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("family_inet6", bgpNeighborOptions.familyInet6); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("graceful_restart", bgpNeighborOptions.gracefulRestart); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("hold_time", bgpNeighborOptions.holdTime); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("import", bgpNeighborOptions.importPolicy); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("keep_all", bgpNeighborOptions.keepAll); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("keep_none", bgpNeighborOptions.keepNone); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_address", bgpNeighborOptions.localAddress); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as", bgpNeighborOptions.localAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_alias", bgpNeighborOptions.localAsAlias); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_loops", bgpNeighborOptions.localAsLoops); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_no_prepend_global_as", bgpNeighborOptions.localAsNoPrependGlobalAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_as_private", bgpNeighborOptions.localAsPrivate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_interface", bgpNeighborOptions.localInterface); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("local_preference", bgpNeighborOptions.localPreference); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("log_updown", bgpNeighborOptions.logUpdown); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out", bgpNeighborOptions.metricOut); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp", bgpNeighborOptions.metricOutIgp); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp_delay_med_update", bgpNeighborOptions.metricOutIgpDelayMedUpdate); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_igp_offset", bgpNeighborOptions.metricOutIgpOffset); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_minimum_igp", bgpNeighborOptions.metricOutMinimumIgp); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("metric_out_minimum_igp_offset", bgpNeighborOptions.metricOutMinimumIgpOffset); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("mtu_discovery", bgpNeighborOptions.mtuDiscovery); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("multihop", bgpNeighborOptions.multihop); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("bgp_multipath", bgpNeighborOptions.bgpMultipath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("no_advertise_peer_as", bgpNeighborOptions.noAdvertisePeerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("out_delay", bgpNeighborOptions.outDelay); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("passive", bgpNeighborOptions.passive); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("peer_as", bgpNeighborOptions.peerAs); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("preference", bgpNeighborOptions.preference); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("remove_private", bgpNeighborOptions.removePrivate); tfErr != nil { - panic(tfErr) - } -} From ef5f7c245efb0d3ca35508b737828a963418058d Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 15 May 2023 09:18:23 +0200 Subject: [PATCH 04/41] r/oam_gretunnel_interface: remove not used variable in a function definition --- internal/providerfwk/resource_oam_gretunnel_interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providerfwk/resource_oam_gretunnel_interface.go b/internal/providerfwk/resource_oam_gretunnel_interface.go index 401c56b6..4820a8e1 100644 --- a/internal/providerfwk/resource_oam_gretunnel_interface.go +++ b/internal/providerfwk/resource_oam_gretunnel_interface.go @@ -287,7 +287,7 @@ func (rsc *oamGretunnelInterface) ImportState( func checkOamGretunnelInterfaceExists( _ context.Context, name string, junSess *junos.Session, ) ( - _ bool, err error, + bool, error, ) { showConfig, err := junSess.Command(junos.CmdShowConfig + "protocols oam gre-tunnel interface " + name + junos.PipeDisplaySet) From 3075e18a5d12243490ba25c9b3f4c69550ee4bb8 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 15 May 2023 12:06:50 +0200 Subject: [PATCH 05/41] r/bgp_*: add new arguments - bgp_error_tolerance - description - no_client_reflect - tcp_aggressive_transmission --- .changes/bgp-with-fwk.md | 2 + docs/resources/bgp_group.md | 34 ++++--- docs/resources/bgp_neighbor.md | 34 ++++--- internal/providerfwk/resource_bgp_group.go | 99 +++++++++++++++++++ .../providerfwk/resource_bgp_group_test.go | 10 ++ internal/providerfwk/resource_bgp_neighbor.go | 99 +++++++++++++++++++ .../providerfwk/resource_bgp_neighbor_test.go | 11 ++- internal/providerfwk/resourcedata_bgp.go | 46 +++++++++ 8 files changed, 310 insertions(+), 25 deletions(-) diff --git a/.changes/bgp-with-fwk.md b/.changes/bgp-with-fwk.md index 756b4289..dff1221c 100644 --- a/.changes/bgp-with-fwk.md +++ b/.changes/bgp-with-fwk.md @@ -9,6 +9,7 @@ ENHANCEMENTS: the resource schema has been upgraded to have one-blocks in single mode instead of list * `advertise_external` is now computed to `true` when `advertise_external_conditional` is `true` (instead of the 2 attributes conflicting) * `bfd_liveness_detection.version` now generate an error if the value is not in one of strings `0`, `1` or `automatic` + * add `bgp_error_tolerance`, `description`, `no_client_reflect`, `tcp_aggressive_transmission` arguments * **resource/junos_bgp_neighbor**: * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) some of config errors are now sent during Plan instead of during Apply @@ -17,3 +18,4 @@ ENHANCEMENTS: the resource schema has been upgraded to have one-blocks in single mode instead of list * `advertise_external` is now computed to `true` when `advertise_external_conditional` is `true` (instead of the 2 attributes conflicting) * `bfd_liveness_detection.version` now generate an error if the value is not in one of strings `0`, `1` or `automatic` + * add `bgp_error_tolerance`, `description`, `no_client_reflect`, `tcp_aggressive_transmission` arguments diff --git a/docs/resources/bgp_group.md b/docs/resources/bgp_group.md index aa3a3060..46423e2c 100644 --- a/docs/resources/bgp_group.md +++ b/docs/resources/bgp_group.md @@ -61,6 +61,16 @@ The following arguments are supported: - **bfd_liveness_detection** (Optional, Block) Define Bidirectional Forwarding Detection (BFD) options. See [below for nested schema](#bfd_liveness_detection-arguments). +- **bgp_error_tolerance** (Optional, Block) + Handle BGP malformed updates softly. + - **malformed_route_limit** (Optional, Number) + Maximum number of malformed routes from a peer (0..4294967295). + Conflict with `no_malformed_route_limit`. + - **malformed_update_log_interval** (Optional, Number) + Time used when logging malformed update (10..65535 seconds). + - **no_malformed_route_limit** (Optional, Boolean) + No malformed route limit. + Conflict with `malformed_route_limit`. - **bgp_multipath** (Optional, Block) Allow load sharing among multiple BGP paths. - **allow_protection** (Optional, Boolean) @@ -74,6 +84,8 @@ The following arguments are supported: Must be a valid IP address. - **damping** (Optional, Boolean) Enable route flap damping. +- **description** (Optional, String) + Text description. - **export** (Optional, List of String) Export policy list. - **family_evpn** (Optional, Block List) @@ -91,7 +103,12 @@ The following arguments are supported: Same options as [`family_inet` arguments](#family_inet-arguments) but for inet6 family. - **graceful_restart** (Optional, Block) Define BGP graceful restart options. - See [below for nested schema](#graceful_restart-arguments). + - **disable** (Optional, Boolean) + Disable graceful restart. + - **restart_time** (Optional, Number) + Restart time used when negotiating with a peer (1..600). + - **stale_route_time** (Optional, Number) + Maximum time for which stale routes are kept (1..600). - **hold_time** (Optional, Number) Hold time used when negotiating with a peer. - **import** (Optional, List of String) @@ -147,6 +164,8 @@ The following arguments are supported: Enable TCP path MTU discovery. - **multihop** (Optional, Boolean) Configure an EBGP multihop session. +- **no_client_reflect** (Optional, Boolean) + Disable intracluster route redistribution. - **out_delay** (Optional, Number) How long before exporting routes from routing table. - **passive** (Optional, Boolean) @@ -157,6 +176,8 @@ The following arguments are supported: Preference value. - **remove_private** (Optional, Boolean) Remove well-known private AS numbers. +- **tcp_aggressive_transmission** (Optional, Boolean) + Enable aggressive transmission of pure TCP ACKs and retransmissions --- @@ -213,17 +234,6 @@ Also for `family_inet6` and `family_evpn` (except `nlri_type`) - **prefix_limit** (Optional, Block) Same options as `accepted_prefix_limit` but for limit maximum number of prefixes from a peer. ---- - -### graceful_restart arguments - -- **disable** (Optional, Boolean) - Disable graceful restart. -- **restart_time** (Optional, Number) - Restart time used when negotiating with a peer (1..600). -- **stale_route_time** (Optional, Number) - Maximum time for which stale routes are kept (1..600). - ## Attributes Reference The following attributes are exported: diff --git a/docs/resources/bgp_neighbor.md b/docs/resources/bgp_neighbor.md index ae947db2..c88a2bcb 100644 --- a/docs/resources/bgp_neighbor.md +++ b/docs/resources/bgp_neighbor.md @@ -60,6 +60,16 @@ The following arguments are supported: - **bfd_liveness_detection** (Optional, Block) Define Bidirectional Forwarding Detection (BFD) options. See [below for nested schema](#bfd_liveness_detection-arguments). +- **bgp_error_tolerance** (Optional, Block) + Handle BGP malformed updates softly. + - **malformed_route_limit** (Optional, Number) + Maximum number of malformed routes from a peer (0..4294967295). + Conflict with `no_malformed_route_limit`. + - **malformed_update_log_interval** (Optional, Number) + Time used when logging malformed update (10..65535 seconds). + - **no_malformed_route_limit** (Optional, Boolean) + No malformed route limit. + Conflict with `malformed_route_limit`. - **bgp_multipath** (Optional, Block) Allow load sharing among multiple BGP paths. - **allow_protection** (Optional, Boolean) @@ -73,6 +83,8 @@ The following arguments are supported: Must be a valid IP address. - **damping** (Optional, Boolean) Enable route flap damping. +- **description** (Optional, String) + Text description. - **export** (Optional, List of String) Export policy list. - **family_evpn** (Optional, Block List) @@ -90,7 +102,12 @@ The following arguments are supported: Same options as [`family_inet` arguments](#family_inet-arguments) but for inet6 family. - **graceful_restart** (Optional, Block) Define BGP graceful restart options. - See [below for nested schema](#graceful_restart-arguments). + - **disable** (Optional, Boolean) + Disable graceful restart. + - **restart_time** (Optional, Number) + Restart time used when negotiating with a peer (1..600). + - **stale_route_time** (Optional, Number) + Maximum time for which stale routes are kept (1..600). - **hold_time** (Optional, Number) Hold time used when negotiating with a peer. - **import** (Optional, List of String) @@ -146,6 +163,8 @@ The following arguments are supported: Enable TCP path MTU discovery. - **multihop** (Optional, Boolean) Configure an EBGP multihop session. +- **no_client_reflect** (Optional, Boolean) + Disable intracluster route redistribution. - **out_delay** (Optional, Number) How long before exporting routes from routing table. - **passive** (Optional, Boolean) @@ -156,6 +175,8 @@ The following arguments are supported: Preference value. - **remove_private** (Optional, Boolean) Remove well-known private AS numbers. +- **tcp_aggressive_transmission** (Optional, Boolean) + Enable aggressive transmission of pure TCP ACKs and retransmissions --- @@ -212,17 +233,6 @@ Also for `family_inet6` and `family_evpn` (except `nlri_type`) - **prefix_limit** (Optional, Block) Same options as `accepted_prefix_limit` but for limit maximum number of prefixes from a peer. ---- - -### graceful_restart arguments - -- **disable** (Optional, Boolean) - Disable graceful restart. -- **restart_time** (Optional, Number) - Restart time used when negotiating with a peer (1..600). -- **stale_route_time** (Optional, Number) - Maximum time for which stale routes are kept (1..600). - ## Attributes Reference The following attributes are exported: diff --git a/internal/providerfwk/resource_bgp_group.go b/internal/providerfwk/resource_bgp_group.go index 557dc011..383dae28 100644 --- a/internal/providerfwk/resource_bgp_group.go +++ b/internal/providerfwk/resource_bgp_group.go @@ -218,6 +218,14 @@ func (rsc *bgpGroup) Schema( tfvalidator.BoolTrue(), }, }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, "export": schema.ListAttribute{ ElementType: types.StringType, Optional: true, @@ -387,6 +395,13 @@ func (rsc *bgpGroup) Schema( tfvalidator.BoolTrue(), }, }, + "no_client_reflect": schema.BoolAttribute{ + Optional: true, + Description: "Disable intracluster route redistribution.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, "out_delay": schema.Int64Attribute{ Optional: true, Description: "How long before exporting routes from routing table.", @@ -423,6 +438,13 @@ func (rsc *bgpGroup) Schema( tfvalidator.BoolTrue(), }, }, + "tcp_aggressive_transmission": schema.BoolAttribute{ + Optional: true, + Description: "Enable aggressive transmission of pure TCP ACKs and retransmissions.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, }, Blocks: map[string]schema.Block{ "bfd_liveness_detection": schema.SingleNestedBlock{ @@ -519,6 +541,35 @@ func (rsc *bgpGroup) Schema( tfplanmodifier.BlockRemoveNull(), }, }, + "bgp_error_tolerance": schema.SingleNestedBlock{ + Description: "Handle BGP malformed updates softly.", + Attributes: map[string]schema.Attribute{ + "malformed_route_limit": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of malformed routes from a peer (0..4294967295).", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "malformed_update_log_interval": schema.Int64Attribute{ + Optional: true, + Description: "Time used when logging malformed update (10..65535 seconds).", + Validators: []validator.Int64{ + int64validator.Between(10, 65535), + }, + }, + "no_malformed_route_limit": schema.BoolAttribute{ + Optional: true, + Description: "No malformed route limit.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, "bgp_multipath": schema.SingleNestedBlock{ Description: "Allow load sharing among multiple BGP paths.", Attributes: map[string]schema.Attribute{ @@ -727,12 +778,15 @@ type bgpGroupData struct { MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` Multihop types.Bool `tfsdk:"multihop"` + NoClientReflect types.Bool `tfsdk:"no_client_reflect"` Passive types.Bool `tfsdk:"passive"` RemovePrivate types.Bool `tfsdk:"remove_private"` + TCPAggressiveTransmission types.Bool `tfsdk:"tcp_aggressive_transmission"` AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` AuthenticationKey types.String `tfsdk:"authentication_key"` AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` Cluster types.String `tfsdk:"cluster"` + Description types.String `tfsdk:"description"` Export []types.String `tfsdk:"export"` HoldTime types.Int64 `tfsdk:"hold_time"` ID types.String `tfsdk:"id"` @@ -752,6 +806,7 @@ type bgpGroupData struct { RoutingInstance types.String `tfsdk:"routing_instance"` Type types.String `tfsdk:"type"` BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpErrorTolerance *bgpBlockBgpErrorTolerance `tfsdk:"bgp_error_tolerance"` BgpMultipath *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` FamilyEvpn []bgpBlockFamily `tfsdk:"family_evpn"` FamilyInet []bgpBlockFamily `tfsdk:"family_inet"` @@ -779,12 +834,15 @@ type bgpGroupConfig struct { MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` Multihop types.Bool `tfsdk:"multihop"` + NoClientReflect types.Bool `tfsdk:"no_client_reflect"` Passive types.Bool `tfsdk:"passive"` RemotePrivate types.Bool `tfsdk:"remove_private"` + TCPAggressiveTransmission types.Bool `tfsdk:"tcp_aggressive_transmission"` AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` AuthenticationKey types.String `tfsdk:"authentication_key"` AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` Cluster types.String `tfsdk:"cluster"` + Description types.String `tfsdk:"description"` Export types.List `tfsdk:"export"` HoldTime types.Int64 `tfsdk:"hold_time"` ID types.String `tfsdk:"id"` @@ -804,6 +862,7 @@ type bgpGroupConfig struct { RoutingInstance types.String `tfsdk:"routing_instance"` Type types.String `tfsdk:"type"` BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpErrorTolerance *bgpBlockBgpErrorTolerance `tfsdk:"bgp_error_tolerance"` BgpMultipah *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` FamilyEvpn types.List `tfsdk:"family_evpn"` FamilyInet types.List `tfsdk:"family_inet"` @@ -962,6 +1021,17 @@ func (rsc *bgpGroup) ValidateConfig( ) } } + if config.BgpErrorTolerance != nil { + if !config.BgpErrorTolerance.MalformedRouteLimit.IsNull() && + !config.BgpErrorTolerance.NoMalformedRouteLimit.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("bgp_error_tolerance").AtName("no_malformed_route_limit"), + tfdiag.ConflictConfigErrSummary, + "malformed_route_limit and no_malformed_route_limit cannot be configured together"+ + " in bgp_error_tolerance block", + ) + } + } if !config.FamilyEvpn.IsNull() && !config.FamilyEvpn.IsUnknown() { var configFamilyEvpn []bgpBlockFamily asDiags := config.FamilyEvpn.ElementsAs(ctx, &configFamilyEvpn, false) @@ -1531,6 +1601,9 @@ func (rscData *bgpGroupData) set( if rscData.Damping.ValueBool() { configSet = append(configSet, setPrefix+"damping") } + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } for _, v := range rscData.Export { configSet = append(configSet, setPrefix+"export "+v.ValueString()) } @@ -1603,6 +1676,9 @@ func (rscData *bgpGroupData) set( if rscData.Multihop.ValueBool() { configSet = append(configSet, setPrefix+"multihop") } + if rscData.NoClientReflect.ValueBool() { + configSet = append(configSet, setPrefix+"no-client-reflect") + } if !rscData.OutDelay.IsNull() { configSet = append(configSet, setPrefix+"out-delay "+ utils.ConvI64toa(rscData.OutDelay.ValueInt64())) @@ -1620,6 +1696,9 @@ func (rscData *bgpGroupData) set( if rscData.RemovePrivate.ValueBool() { configSet = append(configSet, setPrefix+"remove-private") } + if rscData.TCPAggressiveTransmission.ValueBool() { + configSet = append(configSet, setPrefix+"tcp-aggressive-transmission") + } if rscData.BfdLivenessDetection != nil { if rscData.BfdLivenessDetection.isEmpty() { return path.Root("bfd_liveness_detection").AtName("*"), @@ -1628,6 +1707,9 @@ func (rscData *bgpGroupData) set( configSet = append(configSet, rscData.BfdLivenessDetection.configSet(setPrefix)...) } + if rscData.BgpErrorTolerance != nil { + configSet = append(configSet, rscData.BgpErrorTolerance.configSet(setPrefix)...) + } if rscData.BgpMultipath != nil { configSet = append(configSet, rscData.BgpMultipath.configSet(setPrefix)...) } @@ -1750,6 +1832,8 @@ func (rscData *bgpGroupData) read( rscData.Cluster = types.StringValue(itemTrim) case itemTrim == "damping": rscData.Damping = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) case balt.CutPrefixInString(&itemTrim, "export "): rscData.Export = append(rscData.Export, types.StringValue(itemTrim)) case balt.CutPrefixInString(&itemTrim, "hold-time "): @@ -1821,6 +1905,8 @@ func (rscData *bgpGroupData) read( rscData.MtuDiscovery = types.BoolValue(true) case itemTrim == "multihop": rscData.Multihop = types.BoolValue(true) + case itemTrim == "no-client-reflect": + rscData.NoClientReflect = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "out-delay "): rscData.OutDelay, err = tfdata.ConvAtoi64Value(itemTrim) if err != nil { @@ -1837,6 +1923,8 @@ func (rscData *bgpGroupData) read( } case itemTrim == "remove-private": rscData.RemovePrivate = types.BoolValue(true) + case itemTrim == "tcp-aggressive-transmission": + rscData.TCPAggressiveTransmission = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): if rscData.BfdLivenessDetection == nil { rscData.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{} @@ -1844,6 +1932,13 @@ func (rscData *bgpGroupData) read( if err := rscData.BfdLivenessDetection.read(itemTrim); err != nil { return err } + case balt.CutPrefixInString(&itemTrim, "bgp-error-tolerance"): + if rscData.BgpErrorTolerance == nil { + rscData.BgpErrorTolerance = &bgpBlockBgpErrorTolerance{} + } + if err := rscData.BgpErrorTolerance.read(itemTrim); err != nil { + return err + } case balt.CutPrefixInString(&itemTrim, "family evpn "): itemTrimFields := strings.Split(itemTrim, " ") var familyEvpn bgpBlockFamily @@ -1921,6 +2016,7 @@ func (rscData *bgpGroupData) delOpts( delPrefix+"authentication-key-chain", delPrefix+"cluster", delPrefix+"damping", + delPrefix+"description", delPrefix+"export", delPrefix+"hold-time", delPrefix+"import", @@ -1934,12 +2030,15 @@ func (rscData *bgpGroupData) delOpts( delPrefix+"mtu-discovery", delPrefix+"multihop", delPrefix+"multipath", + delPrefix+"no-client-reflect", delPrefix+"out-delay", delPrefix+"passive", delPrefix+"peer-as", delPrefix+"preference", delPrefix+"remove-private", + delPrefix+"tcp-aggressive-transmission", delPrefix+"bfd-liveness-detection", + delPrefix+"bgp-error-tolerance", delPrefix+"family evpn", delPrefix+"family inet", delPrefix+"family inet6", diff --git a/internal/providerfwk/resource_bgp_group_test.go b/internal/providerfwk/resource_bgp_group_test.go index ed4890a5..acffaf0e 100644 --- a/internal/providerfwk/resource_bgp_group_test.go +++ b/internal/providerfwk/resource_bgp_group_test.go @@ -302,6 +302,7 @@ resource "junos_bgp_group" "testacc_bgpgroup" { junos_routing_options.testacc_bgpgroup ] name = "testacc_bgpgroup" + description = "testacc bgpgroup" routing_instance = junos_routing_instance.testacc_bgpgroup.name advertise_external_conditional = true keep_all = true @@ -316,6 +317,8 @@ resource "junos_bgp_group" "testacc_bgpgroup" { restart_time = 10 stale_route_time = 10 } + tcp_aggressive_transmission = true + bgp_error_tolerance {} } ` } @@ -353,6 +356,10 @@ resource "junos_bgp_group" "testacc_bgpgroup" { teardown_idle_timeout = 30 } } + bgp_error_tolerance { + malformed_route_limit = 234 + malformed_update_log_interval = 567 + } } ` } @@ -375,6 +382,9 @@ resource "junos_bgp_group" "testacc_bgpgroup" { local_as_alias = true metric_out_minimum_igp = true family_evpn {} + bgp_error_tolerance { + no_malformed_route_limit = true + } } ` } diff --git a/internal/providerfwk/resource_bgp_neighbor.go b/internal/providerfwk/resource_bgp_neighbor.go index 0234d7a6..ebf04743 100644 --- a/internal/providerfwk/resource_bgp_neighbor.go +++ b/internal/providerfwk/resource_bgp_neighbor.go @@ -216,6 +216,14 @@ func (rsc *bgpNeighbor) Schema( tfvalidator.BoolTrue(), }, }, + "description": schema.StringAttribute{ + Optional: true, + Description: "Text description.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 900), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, "export": schema.ListAttribute{ ElementType: types.StringType, Optional: true, @@ -385,6 +393,13 @@ func (rsc *bgpNeighbor) Schema( tfvalidator.BoolTrue(), }, }, + "no_client_reflect": schema.BoolAttribute{ + Optional: true, + Description: "Disable intracluster route redistribution.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, "out_delay": schema.Int64Attribute{ Optional: true, Description: "How long before exporting routes from routing table.", @@ -421,6 +436,13 @@ func (rsc *bgpNeighbor) Schema( tfvalidator.BoolTrue(), }, }, + "tcp_aggressive_transmission": schema.BoolAttribute{ + Optional: true, + Description: "Enable aggressive transmission of pure TCP ACKs and retransmissions.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, }, Blocks: map[string]schema.Block{ "bfd_liveness_detection": schema.SingleNestedBlock{ @@ -517,6 +539,35 @@ func (rsc *bgpNeighbor) Schema( tfplanmodifier.BlockRemoveNull(), }, }, + "bgp_error_tolerance": schema.SingleNestedBlock{ + Description: "Handle BGP malformed updates softly.", + Attributes: map[string]schema.Attribute{ + "malformed_route_limit": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of malformed routes from a peer (0..4294967295).", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "malformed_update_log_interval": schema.Int64Attribute{ + Optional: true, + Description: "Time used when logging malformed update (10..65535 seconds).", + Validators: []validator.Int64{ + int64validator.Between(10, 65535), + }, + }, + "no_malformed_route_limit": schema.BoolAttribute{ + Optional: true, + Description: "No malformed route limit.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, "bgp_multipath": schema.SingleNestedBlock{ Description: "Allow load sharing among multiple BGP paths.", Attributes: map[string]schema.Attribute{ @@ -725,12 +776,15 @@ type bgpNeighborData struct { MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` Multihop types.Bool `tfsdk:"multihop"` + NoClientReflect types.Bool `tfsdk:"no_client_reflect"` Passive types.Bool `tfsdk:"passive"` RemovePrivate types.Bool `tfsdk:"remove_private"` + TCPAggressiveTransmission types.Bool `tfsdk:"tcp_aggressive_transmission"` AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` AuthenticationKey types.String `tfsdk:"authentication_key"` AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` Cluster types.String `tfsdk:"cluster"` + Description types.String `tfsdk:"description"` Export []types.String `tfsdk:"export"` Group types.String `tfsdk:"group"` HoldTime types.Int64 `tfsdk:"hold_time"` @@ -750,6 +804,7 @@ type bgpNeighborData struct { Preference types.Int64 `tfsdk:"preference"` RoutingInstance types.String `tfsdk:"routing_instance"` BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpErrorTolerance *bgpBlockBgpErrorTolerance `tfsdk:"bgp_error_tolerance"` BgpMultipath *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` FamilyEvpn []bgpBlockFamily `tfsdk:"family_evpn"` FamilyInet []bgpBlockFamily `tfsdk:"family_inet"` @@ -777,12 +832,15 @@ type bgpNeighborConfig struct { MetricOutMinimumIgp types.Bool `tfsdk:"metric_out_minimum_igp"` MtuDiscovery types.Bool `tfsdk:"mtu_discovery"` Multihop types.Bool `tfsdk:"multihop"` + NoClientReflect types.Bool `tfsdk:"no_client_reflect"` Passive types.Bool `tfsdk:"passive"` RemotePrivate types.Bool `tfsdk:"remove_private"` + TCPAggressiveTransmission types.Bool `tfsdk:"tcp_aggressive_transmission"` AuthenticationAlgorithm types.String `tfsdk:"authentication_algorithm"` AuthenticationKey types.String `tfsdk:"authentication_key"` AuthenticationKeyChain types.String `tfsdk:"authentication_key_chain"` Cluster types.String `tfsdk:"cluster"` + Description types.String `tfsdk:"description"` Export types.List `tfsdk:"export"` Group types.String `tfsdk:"group"` HoldTime types.Int64 `tfsdk:"hold_time"` @@ -802,6 +860,7 @@ type bgpNeighborConfig struct { Preference types.Int64 `tfsdk:"preference"` RoutingInstance types.String `tfsdk:"routing_instance"` BfdLivenessDetection *bgpBlockBfdLivenessDetection `tfsdk:"bfd_liveness_detection"` + BgpErrorTolerance *bgpBlockBgpErrorTolerance `tfsdk:"bgp_error_tolerance"` BgpMultipah *bgpBlockBgpMultipath `tfsdk:"bgp_multipath"` FamilyEvpn types.List `tfsdk:"family_evpn"` FamilyInet types.List `tfsdk:"family_inet"` @@ -960,6 +1019,17 @@ func (rsc *bgpNeighbor) ValidateConfig( ) } } + if config.BgpErrorTolerance != nil { + if !config.BgpErrorTolerance.MalformedRouteLimit.IsNull() && + !config.BgpErrorTolerance.NoMalformedRouteLimit.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("bgp_error_tolerance").AtName("no_malformed_route_limit"), + tfdiag.ConflictConfigErrSummary, + "malformed_route_limit and no_malformed_route_limit cannot be configured together"+ + " in bgp_error_tolerance block", + ) + } + } if !config.FamilyEvpn.IsNull() && !config.FamilyEvpn.IsUnknown() { var configFamilyEvpn []bgpBlockFamily asDiags := config.FamilyEvpn.ElementsAs(ctx, &configFamilyEvpn, false) @@ -1576,6 +1646,9 @@ func (rscData *bgpNeighborData) set( if rscData.Damping.ValueBool() { configSet = append(configSet, setPrefix+"damping") } + if v := rscData.Description.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"description \""+v+"\"") + } for _, v := range rscData.Export { configSet = append(configSet, setPrefix+"export "+v.ValueString()) } @@ -1648,6 +1721,9 @@ func (rscData *bgpNeighborData) set( if rscData.Multihop.ValueBool() { configSet = append(configSet, setPrefix+"multihop") } + if rscData.NoClientReflect.ValueBool() { + configSet = append(configSet, setPrefix+"no-client-reflect") + } if !rscData.OutDelay.IsNull() { configSet = append(configSet, setPrefix+"out-delay "+ utils.ConvI64toa(rscData.OutDelay.ValueInt64())) @@ -1665,6 +1741,9 @@ func (rscData *bgpNeighborData) set( if rscData.RemovePrivate.ValueBool() { configSet = append(configSet, setPrefix+"remove-private") } + if rscData.TCPAggressiveTransmission.ValueBool() { + configSet = append(configSet, setPrefix+"tcp-aggressive-transmission") + } if rscData.BfdLivenessDetection != nil { if rscData.BfdLivenessDetection.isEmpty() { return path.Root("bfd_liveness_detection").AtName("*"), @@ -1673,6 +1752,9 @@ func (rscData *bgpNeighborData) set( configSet = append(configSet, rscData.BfdLivenessDetection.configSet(setPrefix)...) } + if rscData.BgpErrorTolerance != nil { + configSet = append(configSet, rscData.BgpErrorTolerance.configSet(setPrefix)...) + } if rscData.BgpMultipath != nil { configSet = append(configSet, rscData.BgpMultipath.configSet(setPrefix)...) } @@ -1800,6 +1882,8 @@ func (rscData *bgpNeighborData) read( rscData.Cluster = types.StringValue(itemTrim) case itemTrim == "damping": rscData.Damping = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "description "): + rscData.Description = types.StringValue(strings.Trim(itemTrim, "\"")) case balt.CutPrefixInString(&itemTrim, "export "): rscData.Export = append(rscData.Export, types.StringValue(itemTrim)) case balt.CutPrefixInString(&itemTrim, "hold-time "): @@ -1871,6 +1955,8 @@ func (rscData *bgpNeighborData) read( rscData.MtuDiscovery = types.BoolValue(true) case itemTrim == "multihop": rscData.Multihop = types.BoolValue(true) + case itemTrim == "no-client-reflect": + rscData.NoClientReflect = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "out-delay "): rscData.OutDelay, err = tfdata.ConvAtoi64Value(itemTrim) if err != nil { @@ -1887,6 +1973,8 @@ func (rscData *bgpNeighborData) read( } case itemTrim == "remove-private": rscData.RemovePrivate = types.BoolValue(true) + case itemTrim == "tcp-aggressive-transmission": + rscData.TCPAggressiveTransmission = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "bfd-liveness-detection "): if rscData.BfdLivenessDetection == nil { rscData.BfdLivenessDetection = &bgpBlockBfdLivenessDetection{} @@ -1894,6 +1982,13 @@ func (rscData *bgpNeighborData) read( if err := rscData.BfdLivenessDetection.read(itemTrim); err != nil { return err } + case balt.CutPrefixInString(&itemTrim, "bgp-error-tolerance"): + if rscData.BgpErrorTolerance == nil { + rscData.BgpErrorTolerance = &bgpBlockBgpErrorTolerance{} + } + if err := rscData.BgpErrorTolerance.read(itemTrim); err != nil { + return err + } case balt.CutPrefixInString(&itemTrim, "family evpn "): itemTrimFields := strings.Split(itemTrim, " ") var familyEvpn bgpBlockFamily @@ -1974,6 +2069,7 @@ func (rscData *bgpNeighborData) delOpts( delPrefix+"authentication-key-chain", delPrefix+"cluster", delPrefix+"damping", + delPrefix+"description", delPrefix+"export", delPrefix+"hold-time", delPrefix+"import", @@ -1987,12 +2083,15 @@ func (rscData *bgpNeighborData) delOpts( delPrefix+"mtu-discovery", delPrefix+"multihop", delPrefix+"multipath", + delPrefix+"no-client-reflect", delPrefix+"out-delay", delPrefix+"passive", delPrefix+"peer-as", delPrefix+"preference", delPrefix+"remove-private", + delPrefix+"tcp-aggressive-transmission", delPrefix+"bfd-liveness-detection", + delPrefix+"bgp-error-tolerance", delPrefix+"family evpn", delPrefix+"family inet", delPrefix+"family inet6", diff --git a/internal/providerfwk/resource_bgp_neighbor_test.go b/internal/providerfwk/resource_bgp_neighbor_test.go index 2eca69b7..e7233fc3 100644 --- a/internal/providerfwk/resource_bgp_neighbor_test.go +++ b/internal/providerfwk/resource_bgp_neighbor_test.go @@ -310,6 +310,7 @@ resource "junos_bgp_neighbor" "testacc_bgpneighbor" { ip = "192.0.2.4" routing_instance = junos_routing_instance.testacc_bgpneighbor.name group = junos_bgp_group.testacc_bgpneighbor.name + description = "peer 2.4" advertise_external_conditional = true keep_none = true no_advertise_peer_as = true @@ -323,8 +324,9 @@ resource "junos_bgp_neighbor" "testacc_bgpneighbor" { restart_time = 10 stale_route_time = 10 } + tcp_aggressive_transmission = true + bgp_error_tolerance {} } - ` } @@ -382,6 +384,10 @@ resource "junos_bgp_neighbor" "testacc_bgpneighbor2b" { teardown_idle_timeout = 30 } } + bgp_error_tolerance { + malformed_route_limit = 234 + malformed_update_log_interval = 567 + } } ` } @@ -426,6 +432,9 @@ resource "junos_bgp_neighbor" "testacc_bgpneighbor2b" { ip = "192.0.2.5" group = junos_bgp_group.testacc_bgpneighbor2b.name family_evpn {} + bgp_error_tolerance { + no_malformed_route_limit = true + } } ` } diff --git a/internal/providerfwk/resourcedata_bgp.go b/internal/providerfwk/resourcedata_bgp.go index fb63dff3..aea15c00 100644 --- a/internal/providerfwk/resourcedata_bgp.go +++ b/internal/providerfwk/resourcedata_bgp.go @@ -59,6 +59,12 @@ func (block *bgpBlockBfdLivenessDetection) isEmpty() bool { } } +type bgpBlockBgpErrorTolerance struct { + NoMalformedRouteLimit types.Bool `tfsdk:"no_malformed_route_limit"` + MalformedRouteLimit types.Int64 `tfsdk:"malformed_route_limit"` + MalformedUpdateLogInterval types.Int64 `tfsdk:"malformed_update_log_interval"` +} + type bgpBlockBgpMultipath struct { AllowProtection types.Bool `tfsdk:"allow_protection"` Disable types.Bool `tfsdk:"disable"` @@ -135,6 +141,27 @@ func (block *bgpBlockBfdLivenessDetection) configSet(setPrefix string) []string return configSet } +func (block *bgpBlockBgpErrorTolerance) configSet(setPrefix string) []string { + setPrefix += "bgp-error-tolerance" + configSet := []string{ + setPrefix, + } + + if !block.MalformedRouteLimit.IsNull() { + configSet = append(configSet, setPrefix+" malformed-route-limit "+ + utils.ConvI64toa(block.MalformedRouteLimit.ValueInt64())) + } + if !block.MalformedUpdateLogInterval.IsNull() { + configSet = append(configSet, setPrefix+" malformed-update-log-interval "+ + utils.ConvI64toa(block.MalformedUpdateLogInterval.ValueInt64())) + } + if block.NoMalformedRouteLimit.ValueBool() { + configSet = append(configSet, setPrefix+" no-malformed-route-limit") + } + + return configSet +} + func (block *bgpBlockBgpMultipath) configSet(setPrefix string) []string { setPrefix += "multipath" configSet := []string{ @@ -344,6 +371,25 @@ func (block *bgpBlockFamily) read(itemTrim string) (err error) { return nil } +func (block *bgpBlockBgpErrorTolerance) read(itemTrim string) (err error) { + switch { + case balt.CutPrefixInString(&itemTrim, " malformed-route-limit "): + block.MalformedRouteLimit, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, " malformed-update-log-interval "): + block.MalformedUpdateLogInterval, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case itemTrim == " no-malformed-route-limit": + block.NoMalformedRouteLimit = types.BoolValue(true) + } + + return nil +} + func (block *bgpBlockBgpMultipath) read(itemTrim string) { switch { case itemTrim == " allow-protection": From e1dfb78009be2676a8807c2aca417cf24848a1e7 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 23 May 2023 09:08:42 +0200 Subject: [PATCH 06/41] r/policyoptions_as_path_group: use new provider via framework --- .changes/policyoptions-with-fwk.md | 7 + docs/resources/policyoptions_as_path_group.md | 14 +- internal/providerfwk/provider.go | 1 + .../resource_policyoptions_as_path_group.go | 431 ++++++++ .../resource_policyoptions_test.go | 973 ++++++++++++++++++ internal/providersdk/provider.go | 1 - .../resource_policyoptions_as_path_group.go | 348 ------- internal/tfdata/find_string.go | 26 + internal/tfdata/find_string_test.go | 60 ++ internal/tfvalidator/string_format.go | 9 + .../string_format_internal_test.go | 10 + 11 files changed, 1525 insertions(+), 355 deletions(-) create mode 100644 .changes/policyoptions-with-fwk.md create mode 100644 internal/providerfwk/resource_policyoptions_as_path_group.go create mode 100644 internal/providerfwk/resource_policyoptions_test.go delete mode 100644 internal/providersdk/resource_policyoptions_as_path_group.go create mode 100644 internal/tfdata/find_string.go create mode 100644 internal/tfdata/find_string_test.go diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md new file mode 100644 index 00000000..748a0870 --- /dev/null +++ b/.changes/policyoptions-with-fwk.md @@ -0,0 +1,7 @@ + +ENHANCEMENTS: + +* **resource/junos_policyoptions_as_path_group**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + some of config errors are now sent during Plan instead of during Apply + optional boolean attributes doesn't accept value *false* + diff --git a/docs/resources/policyoptions_as_path_group.md b/docs/resources/policyoptions_as_path_group.md index e18e5435..9c2ae45f 100644 --- a/docs/resources/policyoptions_as_path_group.md +++ b/docs/resources/policyoptions_as_path_group.md @@ -4,12 +4,12 @@ page_title: "Junos: junos_policyoptions_as_path_group" # junos_policyoptions_as_path_group -Provides an as-path group resource. +Provides a policy-options as-path-group resource. ## Example Usage ```hcl -# Add an as-path group +# Add a policy-options as-path-group resource "junos_policyoptions_as_path_group" "via_century_link" { name = "viaCenturyLink" as_path { @@ -33,18 +33,20 @@ resource "junos_policyoptions_as_path_group" "via_century_link" { ## Argument Reference +-> **Note:** One of `dynamic_db` or `as_path` arguments is required. + The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of as-path group. + Name to identify AS path group. - **as_path** (Optional, Block List) For each name of as-path to declare. - **name** (Required, String) - Name of as-path + Name to identify AS path regular expression. - **path** (Required, String) - As-path + AS path regular expression. - **dynamic_db** (Optional, Boolean) - Add `dynamic-db` parameter. + Object may exist in dynamic database. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 504ad314..2afb442d 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -206,6 +206,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newInterfacePhysicalResource, newInterfaceSt0UnitResource, newOamGretunnelInterfaceResource, + newPolicyoptionsASPathGroupResource, newRoutingInstanceResource, newSecurityResource, newSecurityAddressBookResource, diff --git a/internal/providerfwk/resource_policyoptions_as_path_group.go b/internal/providerfwk/resource_policyoptions_as_path_group.go new file mode 100644 index 00000000..7f948943 --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_as_path_group.go @@ -0,0 +1,431 @@ +package providerfwk + +import ( + "context" + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + + "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" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &policyoptionsASPathGroup{} + _ resource.ResourceWithConfigure = &policyoptionsASPathGroup{} + _ resource.ResourceWithValidateConfig = &policyoptionsASPathGroup{} + _ resource.ResourceWithImportState = &policyoptionsASPathGroup{} +) + +type policyoptionsASPathGroup struct { + client *junos.Client +} + +func newPolicyoptionsASPathGroupResource() resource.Resource { + return &policyoptionsASPathGroup{} +} + +func (rsc *policyoptionsASPathGroup) typeName() string { + return providerName + "_policyoptions_as_path_group" +} + +func (rsc *policyoptionsASPathGroup) junosName() string { + return "policy-options as-path-group" +} + +func (rsc *policyoptionsASPathGroup) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *policyoptionsASPathGroup) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *policyoptionsASPathGroup) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *policyoptionsASPathGroup) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format ``.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name to identify AS path group.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "dynamic_db": schema.BoolAttribute{ + Optional: true, + Description: "Object may exist in dynamic database.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "as_path": schema.ListNestedBlock{ + Description: "For each name of as-path to declare.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Name to identify AS path regular expression.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "path": schema.StringAttribute{ + Required: true, + Description: "AS path regular expression.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.ASPathRegularExpression), + }, + }, + }, + }, + }, + }, + } +} + +type policyoptionsASPathGroupData struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ASPath []policyoptionsASPathGroupBlockASPAth `tfsdk:"as_path"` +} + +type policyoptionsASPathGroupConfig struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ASPath types.List `tfsdk:"as_path"` +} + +type policyoptionsASPathGroupBlockASPAth struct { + Name types.String `tfsdk:"name"` + Path types.String `tfsdk:"path"` +} + +func (rsc *policyoptionsASPathGroup) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config policyoptionsASPathGroupConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.ASPath.IsNull() && + config.DynamicDB.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "at least one of as_path or dynamic_db must be specified", + ) + } + if !config.ASPath.IsNull() && !config.ASPath.IsUnknown() { + var configASPath []policyoptionsASPathGroupBlockASPAth + asDiags := config.ASPath.ElementsAs(ctx, &configASPath, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + asPathName := make(map[string]struct{}) + for i, asPath := range configASPath { + if asPath.Name.IsUnknown() { + continue + } + if _, ok := asPathName[asPath.Name.ValueString()]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("as_path").AtListIndex(i).AtName("name"), + tfdiag.ConflictConfigErrSummary, + fmt.Sprintf("multiple as_path blocks with the same name %q", + asPath.Name.ValueString()), + ) + } + asPathName[asPath.Name.ValueString()] = struct{}{} + } + } +} + +func (rsc *policyoptionsASPathGroup) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan policyoptionsASPathGroupData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + groupExists, err := checkPolicyoptionsAsPathGroupExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if groupExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + groupExists, err := checkPolicyoptionsAsPathGroupExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !groupExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *policyoptionsASPathGroup) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data policyoptionsASPathGroupData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom1String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *policyoptionsASPathGroup) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state policyoptionsASPathGroupData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *policyoptionsASPathGroup) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state policyoptionsASPathGroupData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *policyoptionsASPathGroup) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data policyoptionsASPathGroupData + + var _ resourceDataReadFrom1String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be )", req.ID), + ) +} + +func checkPolicyoptionsAsPathGroupExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options as-path-group \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *policyoptionsASPathGroupData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *policyoptionsASPathGroupData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *policyoptionsASPathGroupData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set policy-options as-path-group \"" + rscData.Name.ValueString() + "\" " + + asPathName := make(map[string]struct{}) + for i, block := range rscData.ASPath { + if _, ok := asPathName[block.Name.ValueString()]; ok { + return path.Root("as_path").AtListIndex(i).AtName("name"), + fmt.Errorf("multiple as_path blocks with the same name %q", block.Name.ValueString()) + } + asPathName[block.Name.ValueString()] = struct{}{} + configSet = append(configSet, setPrefix+ + "as-path \""+block.Name.ValueString()+"\""+ + " \""+block.Path.ValueString()+"\"") + } + if rscData.DynamicDB.ValueBool() { + configSet = append(configSet, setPrefix+"dynamic-db") + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *policyoptionsASPathGroupData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options as-path-group \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case itemTrim == "dynamic-db": + rscData.DynamicDB = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path "): + name := tfdata.FirstElementOfJunosLine(itemTrim) + rscData.ASPath = append(rscData.ASPath, policyoptionsASPathGroupBlockASPAth{ + Name: types.StringValue(strings.Trim(name, "\"")), + Path: types.StringValue(strings.Trim(strings.TrimPrefix(itemTrim, name+" "), "\"")), + }) + } + } + } + + return nil +} + +func (rscData *policyoptionsASPathGroupData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete policy-options as-path-group \"" + rscData.Name.ValueString() + "\"", + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providerfwk/resource_policyoptions_test.go b/internal/providerfwk/resource_policyoptions_test.go new file mode 100644 index 00000000..b72d79ce --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_test.go @@ -0,0 +1,973 @@ +package providerfwk_test + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccJunosPolicyOptions_basic(t *testing.T) { + if os.Getenv("TESTACC_SWITCH") == "" { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: testAccProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccJunosPolicyOptionsConfigCreate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("junos_policyoptions_as_path.testacc_policyOptions", + "path", "5|12|18"), + resource.TestCheckResourceAttr("junos_policyoptions_as_path_group.testacc_policyOptions", + "as_path.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_as_path_group.testacc_policyOptions", + "as_path.0.path", "5|12|18"), + resource.TestCheckResourceAttr("junos_policyoptions_community.testacc_policyOptions", + "members.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_community.testacc_policyOptions", + "members.0", "65000:100"), + resource.TestCheckResourceAttr("junos_policyoptions_prefix_list.testacc_policyOptions", + "prefix.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_prefix_list.testacc_policyOptions", + "prefix.*", "192.0.2.0/25"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.aggregate_contributor", "true"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.bgp_as_path.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.bgp_as_path.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.bgp_community.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.bgp_community.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.bgp_origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.family", "inet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.local_preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.routing_instance", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.interface.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.interface.*", "st0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.metric", "5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.neighbor.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.neighbor.*", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.next_hop.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.next_hop.*", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.ospf_area", "0.0.0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.prefix_list.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.prefix_list.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.protocol.*", "bgp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.#", "2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.0.route", "192.0.2.0/25"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.0.option", "exact"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.1.route", "192.0.2.128/25"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.1.option", "prefix-length-range"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.1.option_value", "/26-/27"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.bgp_as_path.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.bgp_as_path.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.bgp_community.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.bgp_community.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.bgp_origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.family", "inet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.local_preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.routing_instance", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.interface.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.interface.*", "st0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.metric", "5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.neighbor.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.neighbor.*", "192.0.2.5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.next_hop.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.next_hop.*", "192.0.2.5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.ospf_area", "0.0.0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.policy.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.policy.0", "testacc_policyOptions2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.protocol.*", "bgp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.action", "accept"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.as_path_expand", "65000 65000"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.as_path_prepend", "65000 65000"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.#", "3"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.0.action", "set"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.0.value", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.1.action", "delete"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.2.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.default_action", "reject"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.load_balance", "per-packet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.local_preference.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.local_preference.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.next", "policy"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.next_hop", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.metric.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.metric.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.preference.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.preference.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.aggregate_contributor", "true"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.bgp_as_path.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.bgp_as_path.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.bgp_community.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.bgp_community.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.bgp_origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.family", "inet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.local_preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.routing_instance", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.interface.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.interface.*", "st0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.metric", "5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.neighbor.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.neighbor.*", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.next_hop.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.next_hop.*", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.ospf_area", "0.0.0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.policy.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.policy.0", "testacc_policyOptions2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.protocol.*", "bgp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.#", "2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.0.route", "192.0.2.0/25"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.0.option", "exact"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.1.route", "192.0.2.128/25"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.1.option", "prefix-length-range"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.1.option_value", "/26-/27"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.bgp_as_path.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.bgp_as_path.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.bgp_community.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.bgp_community.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.bgp_origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.family", "inet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.local_preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.routing_instance", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.interface.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.interface.*", "st0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.metric", "5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.neighbor.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.neighbor.*", "192.0.2.5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.next_hop.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.next_hop.*", "192.0.2.5"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.ospf_area", "0.0.0.0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.policy.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.policy.0", "testacc_policyOptions2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.preference", "100"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.protocol.*", "bgp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.action", "accept"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.as_path_expand", "last-as count 1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.as_path_prepend", "65000 65000"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.#", "3"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.0.action", "set"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.0.value", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.1.action", "delete"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.2.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.default_action", "reject"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.load_balance", "per-packet"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.local_preference.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.local_preference.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.next", "policy"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.next_hop", "192.0.2.4"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.metric.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.metric.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.origin", "igp"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.preference.0.action", "add"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.preference.0.value", "10"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "from.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "from.0.bgp_as_path_group.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "from.0.bgp_as_path_group.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "to.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "to.0.bgp_as_path_group.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "to.0.bgp_as_path_group.*", "testacc_policyOptions"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.local_preference.0.action", "subtract"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.metric.0.action", "subtract"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.preference.0.action", "subtract"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.local_preference.0.action", "subtract"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.metric.0.action", "subtract"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.preference.0.action", "subtract"), + ), + }, + { + Config: testAccJunosPolicyOptionsConfigUpdate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("junos_policyoptions_as_path.testacc_policyOptions", + "path", "5|15"), + resource.TestCheckResourceAttr("junos_policyoptions_as_path_group.testacc_policyOptions", + "as_path.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_as_path_group.testacc_policyOptions", + "as_path.0.path", "5|15"), + resource.TestCheckResourceAttr("junos_policyoptions_community.testacc_policyOptions", + "members.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_community.testacc_policyOptions", + "members.0", "65000:200"), + resource.TestCheckResourceAttr("junos_policyoptions_prefix_list.testacc_policyOptions", + "prefix.#", "2"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_prefix_list.testacc_policyOptions", + "prefix.*", "192.0.2.0/26"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_prefix_list.testacc_policyOptions", + "prefix.*", "192.0.2.64/26"), + resource.TestCheckResourceAttr("junos_policyoptions_prefix_list.testacc_policyOptions2", + "apply_path", "system radius-server <*>"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.prefix_list.#", "2"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "from.0.route_filter.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "to.0.protocol.*", "ospf"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.community.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.metric.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "then.0.preference.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.from.0.route_filter.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.protocol.#", "1"), + resource.TestCheckTypeSetElemAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.to.0.protocol.*", "ospf"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.community.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.local_preference.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.metric.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions", + "term.0.then.0.preference.#", "0"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.local_preference.0.action", "none"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.metric.0.action", "none"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "then.0.preference.0.action", "none"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.local_preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.local_preference.0.action", "none"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.metric.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.metric.0.action", "none"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.preference.#", "1"), + resource.TestCheckResourceAttr("junos_policyoptions_policy_statement.testacc_policyOptions2", + "term.0.then.0.preference.0.action", "none"), + ), + }, + { + ResourceName: "junos_policyoptions_as_path.testacc_policyOptions", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "junos_policyoptions_as_path_group.testacc_policyOptions", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "junos_policyoptions_community.testacc_policyOptions", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "junos_policyoptions_policy_statement.testacc_policyOptions", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "junos_policyoptions_prefix_list.testacc_policyOptions", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + } +} + +func testAccJunosPolicyOptionsConfigCreate() string { + return ` +resource "junos_routing_instance" "testacc_policyOptions" { + name = "testacc_policyOptions" +} +resource "junos_policyoptions_as_path" "testacc_policyOptions" { + name = "testacc_policyOptions" + path = "5|12|18" +} +resource "junos_policyoptions_as_path_group" "testacc_policyOptions" { + name = "testacc_policyOptions" + as_path { + name = "testacc policyOptions" + path = "5|12|18" + } +} +resource "junos_policyoptions_community" "testacc_policyOptions" { + name = "testacc_policyOptions" + members = ["65000:100"] +} +resource "junos_policyoptions_prefix_list" "testacc_policyOptions" { + name = "testacc_policyOptions" + prefix = ["192.0.2.0/25"] +} +resource "junos_policyoptions_policy_statement" "testacc_policyOptions" { + name = "testacc_policyOptions" + from { + aggregate_contributor = true + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_as_path_calc_length { + count = 4 + match = "orhigher" + } + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_community_count { + count = 6 + match = "orhigher" + } + bgp_origin = "igp" + color = 31 + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.4"] + next_hop = ["192.0.2.4"] + ospf_area = "0.0.0.0" + preference = 100 + prefix_list = [junos_policyoptions_prefix_list.testacc_policyOptions.name] + protocol = ["bgp"] + route_filter { + route = "192.0.2.0/25" + option = "exact" + } + route_filter { + route = "192.0.2.128/25" + option = "prefix-length-range" + option_value = "/26-/27" + } + } + to { + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.5"] + next_hop = ["192.0.2.5"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + protocol = ["bgp"] + } + then { + action = "accept" + as_path_expand = "65000 65000" + as_path_prepend = "65000 65000" + community { + action = "set" + value = junos_policyoptions_community.testacc_policyOptions.name + } + community { + action = "delete" + value = junos_policyoptions_community.testacc_policyOptions.name + } + community { + action = "add" + value = junos_policyoptions_community.testacc_policyOptions.name + } + default_action = "reject" + load_balance = "per-packet" + local_preference { + action = "add" + value = 10 + } + next = "policy" + next_hop = "192.0.2.4" + metric { + action = "add" + value = 10 + } + origin = "igp" + preference { + action = "add" + value = 10 + } + } + term { + name = "term" + from { + aggregate_contributor = true + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.4"] + next_hop = ["192.0.2.4"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + prefix_list = [junos_policyoptions_prefix_list.testacc_policyOptions.name] + protocol = ["bgp"] + route_filter { + route = "192.0.2.0/25" + option = "exact" + } + route_filter { + route = "192.0.2.128/25" + option = "prefix-length-range" + option_value = "/26-/27" + } + } + to { + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.5"] + next_hop = ["192.0.2.5"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + protocol = ["bgp"] + } + then { + action = "accept" + as_path_expand = "last-as count 1" + as_path_prepend = "65000 65000" + community { + action = "set" + value = junos_policyoptions_community.testacc_policyOptions.name + } + community { + action = "delete" + value = junos_policyoptions_community.testacc_policyOptions.name + } + community { + action = "add" + value = junos_policyoptions_community.testacc_policyOptions.name + } + default_action = "reject" + load_balance = "per-packet" + local_preference { + action = "add" + value = 10 + } + next = "policy" + next_hop = "192.0.2.4" + metric { + action = "add" + value = 10 + } + origin = "igp" + preference { + action = "add" + value = 10 + } + } + } +} +resource "junos_policyoptions_policy_statement" "testacc_policyOptions2" { + name = "testacc_policyOptions2" + from { + bgp_as_path_group = [junos_policyoptions_as_path_group.testacc_policyOptions.name] + } + to { + bgp_as_path_group = [junos_policyoptions_as_path_group.testacc_policyOptions.name] + } + then { + local_preference { + action = "subtract" + value = 10 + } + metric { + action = "subtract" + value = 10 + } + preference { + action = "subtract" + value = 10 + } + action = "accept" + } + term { + name = "term" + then { + local_preference { + action = "subtract" + value = 10 + } + metric { + action = "subtract" + value = 10 + } + preference { + action = "subtract" + value = 10 + } + } + } +} +resource "junos_policyoptions_policy_statement" "testacc_policyOptions3" { + name = "testacc_policyOptions3" + add_it_to_forwarding_table_export = true + from { + route_filter { + route = "192.0.2.0/25" + option = "orlonger" + } + } + then { + load_balance = "per-packet" + } +} +` +} + +func testAccJunosPolicyOptionsConfigUpdate() string { + return ` +resource "junos_routing_instance" "testacc_policyOptions" { + name = "testacc_policyOptions" +} +resource "junos_policyoptions_as_path" "testacc_policyOptions" { + name = "testacc_policyOptions" + path = "5|15" +} +resource "junos_policyoptions_as_path_group" "testacc_policyOptions" { + name = "testacc_policyOptions" + as_path { + name = "testacc_policyOptions" + path = "5|15" + } +} +resource "junos_policyoptions_community" "testacc_policyOptions" { + name = "testacc_policyOptions" + members = ["65000:200"] +} +resource "junos_policyoptions_prefix_list" "testacc_policyOptions" { + name = "testacc_policyOptions" + prefix = ["192.0.2.0/26", "192.0.2.64/26"] +} +resource "junos_policyoptions_prefix_list" "testacc_policyOptions2" { + name = "testacc_policyOptions2" + apply_path = "system radius-server <*>" +} +resource "junos_policyoptions_policy_statement" "testacc_policyOptions" { + name = "testacc_policyOptions" + from { + aggregate_contributor = true + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_as_path_calc_length { + count = 4 + match = "orhigher" + } + bgp_as_path_calc_length { + count = 3 + match = "equal" + } + bgp_as_path_unique_count { + count = 3 + match = "equal" + } + bgp_as_path_unique_count { + count = 2 + match = "orhigher" + } + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_community_count { + count = 6 + match = "orhigher" + } + bgp_community_count { + count = 5 + match = "equal" + } + bgp_origin = "igp" + bgp_srte_discriminator = 30 + + evpn_esi = ["00:11:11:11:11:11:11:11:11:33", "00:11:11:11:11:11:11:11:11:32"] + evpn_mac_route = "mac-only" + evpn_tag = [36, 35, 33] + family = "evpn" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.4"] + next_hop = ["192.0.2.4"] + next_hop_type_merged = true + next_hop_weight { + match = "greater-than-equal" + weight = 500 + } + next_hop_weight { + match = "equal" + weight = 200 + } + ospf_area = "0.0.0.0" + preference = 100 + prefix_list = [junos_policyoptions_prefix_list.testacc_policyOptions.name, + junos_policyoptions_prefix_list.testacc_policyOptions2.name, + ] + protocol = ["bgp"] + route_filter { + route = "192.0.2.0/25" + option = "exact" + } + route_type = "internal" + srte_color = 39 + state = "active" + tunnel_type = ["ipip"] + validation_database = "valid" + } + to { + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.5"] + next_hop = ["192.0.2.5"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + protocol = ["ospf"] + } + then { + action = "accept" + as_path_expand = "65000 65000" + as_path_prepend = "65000 65000" + community { + action = "set" + value = junos_policyoptions_community.testacc_policyOptions.name + } + default_action = "reject" + load_balance = "per-packet" + next = "policy" + next_hop = "192.0.2.4" + origin = "igp" + } + term { + name = "term" + from { + aggregate_contributor = true + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_as_path_unique_count { + count = 4 + match = "orlower" + } + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.4"] + next_hop = ["192.0.2.4"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + prefix_list = [junos_policyoptions_prefix_list.testacc_policyOptions.name] + protocol = ["bgp"] + route_filter { + route = "192.0.2.0/25" + option = "exact" + } + } + to { + bgp_as_path = [junos_policyoptions_as_path.testacc_policyOptions.name] + bgp_community = [junos_policyoptions_community.testacc_policyOptions.name] + bgp_origin = "igp" + family = "inet" + local_preference = 100 + routing_instance = junos_routing_instance.testacc_policyOptions.name + interface = ["st0.0"] + metric = 5 + neighbor = ["192.0.2.5"] + next_hop = ["192.0.2.5"] + ospf_area = "0.0.0.0" + policy = [junos_policyoptions_policy_statement.testacc_policyOptions2.name] + preference = 100 + protocol = ["ospf"] + } + then { + action = "accept" + as_path_expand = "last-as count 1" + as_path_prepend = "65000 65000" + default_action = "accept" + load_balance = "per-packet" + next = "policy" + next_hop = "192.0.2.4" + origin = "igp" + } + } +} +resource "junos_policyoptions_policy_statement" "testacc_policyOptions2" { + name = "testacc_policyOptions2" + from { + bgp_as_path_group = [junos_policyoptions_as_path_group.testacc_policyOptions.name] + } + to { + bgp_as_path_group = [junos_policyoptions_as_path_group.testacc_policyOptions.name] + } + then { + local_preference { + action = "none" + value = 10 + } + metric { + action = "none" + value = 10 + } + preference { + action = "none" + value = 10 + } + action = "accept" + } + term { + name = "term" + then { + local_preference { + action = "none" + value = 10 + } + metric { + action = "none" + value = 10 + } + preference { + action = "none" + value = 10 + } + } + } +} +` +} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index c2738974..fbbeb74b 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -171,7 +171,6 @@ func Provider() *schema.Provider { "junos_ospf": resourceOspf(), "junos_ospf_area": resourceOspfArea(), "junos_policyoptions_as_path": resourcePolicyoptionsAsPath(), - "junos_policyoptions_as_path_group": resourcePolicyoptionsAsPathGroup(), "junos_policyoptions_community": resourcePolicyoptionsCommunity(), "junos_policyoptions_policy_statement": resourcePolicyoptionsPolicyStatement(), "junos_policyoptions_prefix_list": resourcePolicyoptionsPrefixList(), diff --git a/internal/providersdk/resource_policyoptions_as_path_group.go b/internal/providersdk/resource_policyoptions_as_path_group.go deleted file mode 100644 index abe87924..00000000 --- a/internal/providersdk/resource_policyoptions_as_path_group.go +++ /dev/null @@ -1,348 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - balt "github.com/jeremmfr/go-utils/basicalter" - bchk "github.com/jeremmfr/go-utils/basiccheck" -) - -type asPathGroupOptions struct { - dynamicDB bool - name string - asPath []map[string]interface{} -} - -func resourcePolicyoptionsAsPathGroup() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourcePolicyoptionsAsPathGroupCreate, - ReadWithoutTimeout: resourcePolicyoptionsAsPathGroupRead, - UpdateWithoutTimeout: resourcePolicyoptionsAsPathGroupUpdate, - DeleteWithoutTimeout: resourcePolicyoptionsAsPathGroupDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourcePolicyoptionsAsPathGroupImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "as_path": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "path": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "dynamic_db": { - Type: schema.TypeBool, - Optional: true, - }, - }, - } -} - -func resourcePolicyoptionsAsPathGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setPolicyoptionsAsPathGroup(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("name").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - policyoptsAsPathGroupExists, err := checkPolicyoptionsAsPathGroupExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsAsPathGroupExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("policy-options as-path-group %v already exists", d.Get("name").(string)))...) - } - - if err := setPolicyoptionsAsPathGroup(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_policyoptions_as_path_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - policyoptsAsPathGroupExists, err = checkPolicyoptionsAsPathGroupExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsAsPathGroupExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("policy-options as-path-group %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourcePolicyoptionsAsPathGroupReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsAsPathGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourcePolicyoptionsAsPathGroupReadWJunSess(d, junSess) -} - -func resourcePolicyoptionsAsPathGroupReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - asPathGroupOptions, err := readPolicyoptionsAsPathGroup(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if asPathGroupOptions.name == "" { - d.SetId("") - } else { - fillPolicyoptionsAsPathGroupData(d, asPathGroupOptions) - } - - return nil -} - -func resourcePolicyoptionsAsPathGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsAsPathGroup(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setPolicyoptionsAsPathGroup(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsAsPathGroup(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setPolicyoptionsAsPathGroup(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_policyoptions_as_path_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourcePolicyoptionsAsPathGroupReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsAsPathGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsAsPathGroup(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsAsPathGroup(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_policyoptions_as_path_group") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourcePolicyoptionsAsPathGroupImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - - policyoptsAsPathGroupExists, err := checkPolicyoptionsAsPathGroupExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !policyoptsAsPathGroupExists { - return nil, fmt.Errorf("don't find policy-options as-path-group with id '%v' (id must be )", d.Id()) - } - asPathGroupOptions, err := readPolicyoptionsAsPathGroup(d.Id(), junSess) - if err != nil { - return nil, err - } - fillPolicyoptionsAsPathGroupData(d, asPathGroupOptions) - - result[0] = d - - return result, nil -} - -func checkPolicyoptionsAsPathGroupExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "policy-options as-path-group " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setPolicyoptionsAsPathGroup(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - setPrefix := "set policy-options as-path-group " + d.Get("name").(string) - asPathNameList := make([]string, 0) - for _, v := range d.Get("as_path").([]interface{}) { - asPath := v.(map[string]interface{}) - if bchk.InSlice(asPath["name"].(string), asPathNameList) { - return fmt.Errorf("multiple blocks as_path with the same name %s", asPath["name"].(string)) - } - asPathNameList = append(asPathNameList, asPath["name"].(string)) - configSet = append(configSet, setPrefix+ - " as-path "+asPath["name"].(string)+ - " \""+asPath["path"].(string)+"\"") - } - if d.Get("dynamic_db").(bool) { - configSet = append(configSet, setPrefix+" dynamic-db") - } - - return junSess.ConfigSet(configSet) -} - -func readPolicyoptionsAsPathGroup(name string, junSess *junos.Session, -) (confRead asPathGroupOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "policy-options as-path-group " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - for _, item := range strings.Split(showConfig, "\n") { - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - itemTrim := strings.TrimPrefix(item, junos.SetLS) - switch { - case itemTrim == "dynamic-db": - confRead.dynamicDB = true - case balt.CutPrefixInString(&itemTrim, "as-path "): - itemTrimFields := strings.Split(itemTrim, " ") - asPath := map[string]interface{}{ - "name": itemTrimFields[0], - "path": strings.Trim(strings.TrimPrefix(itemTrim, itemTrimFields[0]+" "), "\""), - } - confRead.asPath = append(confRead.asPath, asPath) - } - } - } - - return confRead, nil -} - -func delPolicyoptionsAsPathGroup(asPathGroup string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete policy-options as-path-group "+asPathGroup) - - return junSess.ConfigSet(configSet) -} - -func fillPolicyoptionsAsPathGroupData(d *schema.ResourceData, asPathGroupOptions asPathGroupOptions) { - if tfErr := d.Set("name", asPathGroupOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("as_path", asPathGroupOptions.asPath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("dynamic_db", asPathGroupOptions.dynamicDB); tfErr != nil { - panic(tfErr) - } -} diff --git a/internal/tfdata/find_string.go b/internal/tfdata/find_string.go new file mode 100644 index 00000000..0162b0c0 --- /dev/null +++ b/internal/tfdata/find_string.go @@ -0,0 +1,26 @@ +package tfdata + +import "strings" + +func FirstElementOfJunosLine(itemTrim string) string { + if !strings.HasPrefix(itemTrim, `"`) { + return strings.Split(itemTrim, ` `)[0] + } + + itemTrimFields := strings.Split(itemTrim, ` `) + var first string + for i, v := range itemTrimFields { + first += v + if i == 0 && v == `"` { + first += ` ` + + continue + } + if strings.HasSuffix(v, `"`) { + return first + } + first += ` ` + } + + return first +} diff --git a/internal/tfdata/find_string_test.go b/internal/tfdata/find_string_test.go new file mode 100644 index 00000000..5394e728 --- /dev/null +++ b/internal/tfdata/find_string_test.go @@ -0,0 +1,60 @@ +package tfdata_test + +import ( + "testing" + + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" +) + +func TestFirstElementOfJunosLine(t *testing.T) { + t.Parallel() + + type testCase struct { + inputStr string + expectOutput string + } + + tests := map[string]testCase{ + "Simple": { + inputStr: `foo bar`, + expectOutput: `foo`, + }, + "With double quote": { + inputStr: `"foo" bar`, + expectOutput: `"foo"`, + }, + "With double quote and space": { + inputStr: `"foo baz" bar`, + expectOutput: `"foo baz"`, + }, + "With double quote and multiple spaces": { + inputStr: `" foo baz " bar`, + expectOutput: `" foo baz "`, + }, + "With double quote and space and other word with double quote": { + inputStr: `"foo baz" "bar qux"`, + expectOutput: `"foo baz"`, + }, + "One word": { + inputStr: `foo`, + expectOutput: `foo`, + }, + "One word with double quote": { + inputStr: `"foo"`, + expectOutput: `"foo"`, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + output := tfdata.FirstElementOfJunosLine(test.inputStr) + + if output != test.expectOutput { + t.Errorf("expected %s, got %s", test.expectOutput, output) + } + }) + } +} diff --git a/internal/tfvalidator/string_format.go b/internal/tfvalidator/string_format.go index 59870c86..7c0d3ac5 100644 --- a/internal/tfvalidator/string_format.go +++ b/internal/tfvalidator/string_format.go @@ -18,6 +18,7 @@ const ( DNSNameFormat InterfaceFormat HexadecimalFormat + ASPathRegularExpression ) func (f stringFormat) invalidRune() func(rune) bool { @@ -45,6 +46,12 @@ func (f stringFormat) invalidRune() func(rune) bool { return func(r rune) bool { return (r < 'a' || r > 'f') && (r < 'A' || r > 'F') && (r < '0' || r > '9') } + case ASPathRegularExpression: + return func(r rune) bool { + return r != '^' && r != '$' && r != '-' && r != ',' && r != '|' && r != '*' && r != '!' && + r != '+' && r != '?' && r != '{' && r != '}' && r != '.' && r != '[' && r != ']' && + r != '(' && r != ')' && (r < '0' || r > '9') && r != ' ' + } default: return func(r rune) bool { return true @@ -64,6 +71,8 @@ func (f stringFormat) String() string { return "letters, numbers, dashes, slashes, dots and colons" case HexadecimalFormat: return "A-F or a-f letters and numbers" + case ASPathRegularExpression: + return "regular expression characters, numbers and spaces" default: return "" } diff --git a/internal/tfvalidator/string_format_internal_test.go b/internal/tfvalidator/string_format_internal_test.go index 4b85ac33..bf18edee 100644 --- a/internal/tfvalidator/string_format_internal_test.go +++ b/internal/tfvalidator/string_format_internal_test.go @@ -91,6 +91,16 @@ func TestStringFormat(t *testing.T) { expectError: true, sensitive: true, }, + "ASPathRegularExpression_valid": { + val: types.StringValue(".* 209 .*"), + format: ASPathRegularExpression, + expectError: false, + }, + "ASPathRegularExpression_invalid": { + val: types.StringValue(".* AS209 .*"), + format: ASPathRegularExpression, + expectError: true, + }, } for name, test := range tests { From 558cdfe906c427e811bdf0ae350eb3aa179b4eb8 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 23 May 2023 09:10:22 +0200 Subject: [PATCH 07/41] r/security_ipsec_vpn: fix validators of name for traffic_selector block - fix length validator (max 31 instead of 32) - remove space exclusion validator --- .changes/policyoptions-with-fwk.md | 3 +++ .../providerfwk/resource_security_ike_ipsec_test.go | 2 +- internal/providerfwk/resource_security_ipsec_vpn.go | 11 +++++------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index 748a0870..9da1f617 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -5,3 +5,6 @@ ENHANCEMENTS: some of config errors are now sent during Plan instead of during Apply optional boolean attributes doesn't accept value *false* +BUG FIXES: + +* **resource/junos_security_ipsec_vpn**: fix length validator (max 31 instead of 32) and remove space exclusion validator of `name` for `traffic_selector` block diff --git a/internal/providerfwk/resource_security_ike_ipsec_test.go b/internal/providerfwk/resource_security_ike_ipsec_test.go index 5eaff89b..b43474b6 100644 --- a/internal/providerfwk/resource_security_ike_ipsec_test.go +++ b/internal/providerfwk/resource_security_ike_ipsec_test.go @@ -631,7 +631,7 @@ resource "junos_security_ipsec_vpn" "testacc_ipsecvpn" { remote_ip = "192.0.3.64/26" } traffic_selector { - name = "ts-2" + name = "ts 2" local_ip = "192.0.2.128/26" remote_ip = "192.0.3.192/26" } diff --git a/internal/providerfwk/resource_security_ipsec_vpn.go b/internal/providerfwk/resource_security_ipsec_vpn.go index 966abc68..ee81f9fd 100644 --- a/internal/providerfwk/resource_security_ipsec_vpn.go +++ b/internal/providerfwk/resource_security_ipsec_vpn.go @@ -296,9 +296,8 @@ func (rsc *securityIpsecVpn) Schema( Required: true, Description: "Name of traffic-selector.", Validators: []validator.String{ - stringvalidator.LengthBetween(1, 32), + stringvalidator.LengthBetween(1, 31), tfvalidator.StringDoubleQuoteExclusion(), - tfvalidator.StringSpaceExclusion(), }, }, "local_ip": schema.StringAttribute{ @@ -1099,12 +1098,12 @@ func (rscData *securityIpsecVpnData) read( } } case balt.CutPrefixInString(&itemTrim, "traffic-selector "): - itemTrimFields := strings.Split(itemTrim, " ") + name := tfdata.FirstElementOfJunosLine(itemTrim) var trafficSelector securityIpsecVpnBlockTrafficSelector rscData.TrafficSelector, trafficSelector = tfdata.ExtractBlockWithTFTypesString( - rscData.TrafficSelector, "Name", strings.Trim(itemTrimFields[0], "\"")) - trafficSelector.Name = types.StringValue(strings.Trim(itemTrimFields[0], "\"")) - balt.CutPrefixInString(&itemTrim, itemTrimFields[0]+" ") + rscData.TrafficSelector, "Name", strings.Trim(name, "\"")) + trafficSelector.Name = types.StringValue(strings.Trim(name, "\"")) + balt.CutPrefixInString(&itemTrim, name+" ") switch { case balt.CutPrefixInString(&itemTrim, "local-ip "): trafficSelector.LocalIP = types.StringValue(itemTrim) From ff9acc59509251b2e21ac88b44ff6085bacdd140 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Tue, 23 May 2023 19:13:22 +0200 Subject: [PATCH 08/41] r/policyoptions_as_path: use new provider via framework --- .changes/policyoptions-with-fwk.md | 3 + docs/resources/policyoptions_as_path.md | 12 +- internal/providerfwk/provider.go | 1 + .../resource_policyoptions_as_path.go | 363 ++++++++++++++++++ internal/providersdk/provider.go | 1 - .../resource_policyoptions_as_path.go | 317 --------------- 6 files changed, 374 insertions(+), 323 deletions(-) create mode 100644 internal/providerfwk/resource_policyoptions_as_path.go delete mode 100644 internal/providersdk/resource_policyoptions_as_path.go diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index 9da1f617..ee5eafc8 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -1,6 +1,9 @@ ENHANCEMENTS: +* **resource/junos_policyoptions_as_path**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + some of config errors are now sent during Plan instead of during Apply + optional boolean attributes doesn't accept value *false* * **resource/junos_policyoptions_as_path_group**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) some of config errors are now sent during Plan instead of during Apply optional boolean attributes doesn't accept value *false* diff --git a/docs/resources/policyoptions_as_path.md b/docs/resources/policyoptions_as_path.md index 651556f0..f61fec82 100644 --- a/docs/resources/policyoptions_as_path.md +++ b/docs/resources/policyoptions_as_path.md @@ -4,12 +4,12 @@ page_title: "Junos: junos_policyoptions_as_path" # junos_policyoptions_as_path -Provides an as-path resource. +Provides a policy-options as-path resource. ## Example Usage ```hcl -# Add an as-path +# Add a policy-options as-path resource "junos_policyoptions_as_path" "github" { name = "github" path = ".* 36459" @@ -18,14 +18,16 @@ resource "junos_policyoptions_as_path" "github" { ## Argument Reference +-> **Note:** One of `dynamic_db` or `path` arguments is required. + The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of as-path. + Name to identify AS path regular expression. - **dynamic_db** (Optional, Boolean) - Add `dynamic-db` parameter. + Object may exist in dynamic database. - **path** (Optional, String) - As-path. + AS path regular expression. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index 2afb442d..d0343b97 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -206,6 +206,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newInterfacePhysicalResource, newInterfaceSt0UnitResource, newOamGretunnelInterfaceResource, + newPolicyoptionsASPathResource, newPolicyoptionsASPathGroupResource, newRoutingInstanceResource, newSecurityResource, diff --git a/internal/providerfwk/resource_policyoptions_as_path.go b/internal/providerfwk/resource_policyoptions_as_path.go new file mode 100644 index 00000000..832fb511 --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_as_path.go @@ -0,0 +1,363 @@ +package providerfwk + +import ( + "context" + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + + "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" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &policyoptionsASPath{} + _ resource.ResourceWithConfigure = &policyoptionsASPath{} + _ resource.ResourceWithValidateConfig = &policyoptionsASPath{} + _ resource.ResourceWithImportState = &policyoptionsASPath{} +) + +type policyoptionsASPath struct { + client *junos.Client +} + +func newPolicyoptionsASPathResource() resource.Resource { + return &policyoptionsASPath{} +} + +func (rsc *policyoptionsASPath) typeName() string { + return providerName + "_policyoptions_as_path" +} + +func (rsc *policyoptionsASPath) junosName() string { + return "policy-options as-path" +} + +func (rsc *policyoptionsASPath) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *policyoptionsASPath) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *policyoptionsASPath) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *policyoptionsASPath) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format ``.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name to identify AS path regular expression.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "dynamic_db": schema.BoolAttribute{ + Optional: true, + Description: "Object may exist in dynamic database.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "path": schema.StringAttribute{ + Optional: true, + Description: "AS path regular expression.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.ASPathRegularExpression), + }, + }, + }, + } +} + +type policyoptionsASPathData struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Path types.String `tfsdk:"path"` +} + +func (rsc *policyoptionsASPath) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config policyoptionsASPathData + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.Path.IsNull() && + config.DynamicDB.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "at least one of path or dynamic_db must be specified", + ) + } +} + +func (rsc *policyoptionsASPath) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan policyoptionsASPathData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + asPathExists, err := checkPolicyoptionsAsPathExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if asPathExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + asPathExists, err := checkPolicyoptionsAsPathExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !asPathExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *policyoptionsASPath) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data policyoptionsASPathData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom1String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *policyoptionsASPath) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state policyoptionsASPathData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *policyoptionsASPath) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state policyoptionsASPathData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *policyoptionsASPath) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data policyoptionsASPathData + + var _ resourceDataReadFrom1String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be )", req.ID), + ) +} + +func checkPolicyoptionsAsPathExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options as-path \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *policyoptionsASPathData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *policyoptionsASPathData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *policyoptionsASPathData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set policy-options as-path \"" + rscData.Name.ValueString() + "\" " + + if rscData.DynamicDB.ValueBool() { + configSet = append(configSet, setPrefix+"dynamic-db") + } + if v := rscData.Path.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"\""+v+"\"") + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *policyoptionsASPathData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options as-path \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case itemTrim == "dynamic-db": + rscData.DynamicDB = types.BoolValue(true) + default: + rscData.Path = types.StringValue(strings.Trim(itemTrim, "\"")) + } + } + } + + return nil +} + +func (rscData *policyoptionsASPathData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete policy-options as-path \"" + rscData.Name.ValueString() + "\"", + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index fbbeb74b..b5bb2a98 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -170,7 +170,6 @@ func Provider() *schema.Provider { "junos_null_commit_file": resourceNullCommitFile(), "junos_ospf": resourceOspf(), "junos_ospf_area": resourceOspfArea(), - "junos_policyoptions_as_path": resourcePolicyoptionsAsPath(), "junos_policyoptions_community": resourcePolicyoptionsCommunity(), "junos_policyoptions_policy_statement": resourcePolicyoptionsPolicyStatement(), "junos_policyoptions_prefix_list": resourcePolicyoptionsPrefixList(), diff --git a/internal/providersdk/resource_policyoptions_as_path.go b/internal/providersdk/resource_policyoptions_as_path.go deleted file mode 100644 index 036e3a22..00000000 --- a/internal/providersdk/resource_policyoptions_as_path.go +++ /dev/null @@ -1,317 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -type asPathOptions struct { - dynamicDB bool - name string - path string -} - -func resourcePolicyoptionsAsPath() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourcePolicyoptionsAsPathCreate, - ReadWithoutTimeout: resourcePolicyoptionsAsPathRead, - UpdateWithoutTimeout: resourcePolicyoptionsAsPathUpdate, - DeleteWithoutTimeout: resourcePolicyoptionsAsPathDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourcePolicyoptionsAsPathImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "dynamic_db": { - Type: schema.TypeBool, - Optional: true, - }, - "path": { - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourcePolicyoptionsAsPathCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setPolicyoptionsAsPath(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("name").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - policyoptsAsPathExists, err := checkPolicyoptionsAsPathExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsAsPathExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("policy-options as-path %v already exists", d.Get("name").(string)))...) - } - - if err := setPolicyoptionsAsPath(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_policyoptions_as_path") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - policyoptsAsPathExists, err = checkPolicyoptionsAsPathExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsAsPathExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("policy-options as-path %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourcePolicyoptionsAsPathReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsAsPathRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourcePolicyoptionsAsPathReadWJunSess(d, junSess) -} - -func resourcePolicyoptionsAsPathReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - asPathOptions, err := readPolicyoptionsAsPath(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if asPathOptions.name == "" { - d.SetId("") - } else { - fillPolicyoptionsAsPathData(d, asPathOptions) - } - - return nil -} - -func resourcePolicyoptionsAsPathUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsAsPath(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setPolicyoptionsAsPath(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsAsPath(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setPolicyoptionsAsPath(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_policyoptions_as_path") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourcePolicyoptionsAsPathReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsAsPathDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsAsPath(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsAsPath(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_policyoptions_as_path") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourcePolicyoptionsAsPathImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - - policyoptsAsPathExists, err := checkPolicyoptionsAsPathExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !policyoptsAsPathExists { - return nil, fmt.Errorf("don't find policy-options as-path with id '%v' (id must be )", d.Id()) - } - asPathOptions, err := readPolicyoptionsAsPath(d.Id(), junSess) - if err != nil { - return nil, err - } - fillPolicyoptionsAsPathData(d, asPathOptions) - - result[0] = d - - return result, nil -} - -func checkPolicyoptionsAsPathExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + "policy-options as-path " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setPolicyoptionsAsPath(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - if d.Get("dynamic_db").(bool) { - configSet = append(configSet, "set policy-options as-path "+d.Get("name").(string)+ - " dynamic-db") - } - if d.Get("path").(string) != "" { - configSet = append(configSet, "set policy-options as-path "+d.Get("name").(string)+ - " \""+d.Get("path").(string)+"\"") - } - - return junSess.ConfigSet(configSet) -} - -func readPolicyoptionsAsPath(name string, junSess *junos.Session, -) (confRead asPathOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "policy-options as-path " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - for _, item := range strings.Split(showConfig, "\n") { - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - itemTrim := strings.TrimPrefix(item, junos.SetLS) - switch { - case itemTrim == "dynamic-db": - confRead.dynamicDB = true - default: - confRead.path = strings.Trim(itemTrim, "\"") - } - } - } - - return confRead, nil -} - -func delPolicyoptionsAsPath(asPath string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete policy-options as-path "+asPath) - - return junSess.ConfigSet(configSet) -} - -func fillPolicyoptionsAsPathData(d *schema.ResourceData, asPathOptions asPathOptions) { - if tfErr := d.Set("name", asPathOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("dynamic_db", asPathOptions.dynamicDB); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("path", asPathOptions.path); tfErr != nil { - panic(tfErr) - } -} From 6302800ff19eb57f444f6581de24f8f77f9ade1c Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 24 May 2023 09:17:24 +0200 Subject: [PATCH 09/41] r/policyoptions_community: use new provider via framework --- .changes/policyoptions-with-fwk.md | 3 + docs/resources/policyoptions_community.md | 10 +- internal/providerfwk/provider.go | 1 + .../resource_policyoptions_community.go | 350 ++++++++++++++++++ internal/providersdk/provider.go | 1 - .../resource_policyoptions_community.go | 319 ---------------- 6 files changed, 359 insertions(+), 325 deletions(-) create mode 100644 internal/providerfwk/resource_policyoptions_community.go delete mode 100644 internal/providersdk/resource_policyoptions_community.go diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index ee5eafc8..edd56208 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -7,6 +7,9 @@ ENHANCEMENTS: * **resource/junos_policyoptions_as_path_group**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) some of config errors are now sent during Plan instead of during Apply optional boolean attributes doesn't accept value *false* +* **resource/junos_policyoptions_community**: + * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional boolean attributes doesn't accept value *false* BUG FIXES: diff --git a/docs/resources/policyoptions_community.md b/docs/resources/policyoptions_community.md index efd4e7b2..12810821 100644 --- a/docs/resources/policyoptions_community.md +++ b/docs/resources/policyoptions_community.md @@ -4,12 +4,12 @@ page_title: "Junos: junos_policyoptions_community" # junos_policyoptions_community -Provides a community BGP resource. +Provides a policy-options community resource. ## Example Usage ```hcl -# Add a community +# Add a policy-options community resource "junos_policyoptions_community" "community_demo" { name = "communityDemo" members = ["65000:100"] @@ -21,11 +21,11 @@ resource "junos_policyoptions_community" "community_demo" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of community. + Name to identify BGP community. - **members** (Required, List of String) - List of community. + Community members. - **invert_match** (Optional, Boolean) - Add `invert-match` parameter. + Invert the result of the community expression matching. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index d0343b97..e8328d8b 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -208,6 +208,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newOamGretunnelInterfaceResource, newPolicyoptionsASPathResource, newPolicyoptionsASPathGroupResource, + newPolicyoptionsCommunityResource, newRoutingInstanceResource, newSecurityResource, newSecurityAddressBookResource, diff --git a/internal/providerfwk/resource_policyoptions_community.go b/internal/providerfwk/resource_policyoptions_community.go new file mode 100644 index 00000000..c08a7a40 --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_community.go @@ -0,0 +1,350 @@ +package providerfwk + +import ( + "context" + "fmt" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "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" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &policyoptionsCommunity{} + _ resource.ResourceWithConfigure = &policyoptionsCommunity{} + _ resource.ResourceWithImportState = &policyoptionsCommunity{} +) + +type policyoptionsCommunity struct { + client *junos.Client +} + +func newPolicyoptionsCommunityResource() resource.Resource { + return &policyoptionsCommunity{} +} + +func (rsc *policyoptionsCommunity) typeName() string { + return providerName + "_policyoptions_community" +} + +func (rsc *policyoptionsCommunity) junosName() string { + return "policy-options community" +} + +func (rsc *policyoptionsCommunity) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *policyoptionsCommunity) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *policyoptionsCommunity) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *policyoptionsCommunity) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format ``.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name to identify BGP community.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "members": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + Description: "Community members.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "invert_match": schema.BoolAttribute{ + Optional: true, + Description: "Invert the result of the community expression matching.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + } +} + +type policyoptionsCommunityData struct { + InvertMatch types.Bool `tfsdk:"invert_match"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Members []types.String `tfsdk:"members"` +} + +func (rsc *policyoptionsCommunity) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan policyoptionsCommunityData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + communityExists, err := checkPolicyoptionsCommunityExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if communityExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + communityExists, err := checkPolicyoptionsCommunityExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !communityExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *policyoptionsCommunity) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data policyoptionsCommunityData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom1String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *policyoptionsCommunity) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state policyoptionsCommunityData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *policyoptionsCommunity) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state policyoptionsCommunityData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *policyoptionsCommunity) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data policyoptionsCommunityData + + var _ resourceDataReadFrom1String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be )", req.ID), + ) +} + +func checkPolicyoptionsCommunityExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options community \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *policyoptionsCommunityData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *policyoptionsCommunityData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *policyoptionsCommunityData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set policy-options community \"" + rscData.Name.ValueString() + "\" " + + for _, v := range rscData.Members { + configSet = append(configSet, setPrefix+"members \""+v.ValueString()+"\"") + } + if rscData.InvertMatch.ValueBool() { + configSet = append(configSet, setPrefix+"invert-match") + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *policyoptionsCommunityData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options community \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case balt.CutPrefixInString(&itemTrim, "members "): + rscData.Members = append(rscData.Members, + types.StringValue(strings.Trim(itemTrim, "\""))) + case itemTrim == "invert-match": + rscData.InvertMatch = types.BoolValue(true) + } + } + } + + return nil +} + +func (rscData *policyoptionsCommunityData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete policy-options community \"" + rscData.Name.ValueString() + "\"", + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index b5bb2a98..aa42bada 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -170,7 +170,6 @@ func Provider() *schema.Provider { "junos_null_commit_file": resourceNullCommitFile(), "junos_ospf": resourceOspf(), "junos_ospf_area": resourceOspfArea(), - "junos_policyoptions_community": resourcePolicyoptionsCommunity(), "junos_policyoptions_policy_statement": resourcePolicyoptionsPolicyStatement(), "junos_policyoptions_prefix_list": resourcePolicyoptionsPrefixList(), "junos_rib_group": resourceRibGroup(), diff --git a/internal/providersdk/resource_policyoptions_community.go b/internal/providersdk/resource_policyoptions_community.go deleted file mode 100644 index 445f76f7..00000000 --- a/internal/providersdk/resource_policyoptions_community.go +++ /dev/null @@ -1,319 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - balt "github.com/jeremmfr/go-utils/basicalter" -) - -type communityOptions struct { - invertMatch bool - name string - members []string -} - -func resourcePolicyoptionsCommunity() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourcePolicyoptionsCommunityCreate, - ReadWithoutTimeout: resourcePolicyoptionsCommunityRead, - UpdateWithoutTimeout: resourcePolicyoptionsCommunityUpdate, - DeleteWithoutTimeout: resourcePolicyoptionsCommunityDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourcePolicyoptionsCommunityImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "members": { - Type: schema.TypeList, - Required: true, - MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "invert_match": { - Type: schema.TypeBool, - Optional: true, - }, - }, - } -} - -func resourcePolicyoptionsCommunityCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setPolicyoptionsCommunity(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("name").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - policyoptsCommunityExists, err := checkPolicyoptionsCommunityExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsCommunityExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("policy-options community %v already exists", d.Get("name").(string)))...) - } - - if err := setPolicyoptionsCommunity(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_policyoptions_community") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - policyoptsCommunityExists, err = checkPolicyoptionsCommunityExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsCommunityExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("policy-options community %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourcePolicyoptionsCommunityReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsCommunityRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourcePolicyoptionsCommunityReadWJunSess(d, junSess) -} - -func resourcePolicyoptionsCommunityReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - communityOptions, err := readPolicyoptionsCommunity(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if communityOptions.name == "" { - d.SetId("") - } else { - fillPolicyoptionsCommunityData(d, communityOptions) - } - - return nil -} - -func resourcePolicyoptionsCommunityUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsCommunity(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setPolicyoptionsCommunity(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsCommunity(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setPolicyoptionsCommunity(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_policyoptions_community") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourcePolicyoptionsCommunityReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsCommunityDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsCommunity(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsCommunity(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_policyoptions_community") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourcePolicyoptionsCommunityImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - - policyoptsCommunityExists, err := checkPolicyoptionsCommunityExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !policyoptsCommunityExists { - return nil, fmt.Errorf("don't find policy-options community with id '%v' (id must be )", d.Id()) - } - communityOptions, err := readPolicyoptionsCommunity(d.Id(), junSess) - if err != nil { - return nil, err - } - fillPolicyoptionsCommunityData(d, communityOptions) - - result[0] = d - - return result, nil -} - -func checkPolicyoptionsCommunityExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + "policy-options community " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setPolicyoptionsCommunity(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - setPrefix := "set policy-options community " + d.Get("name").(string) + " " - for _, v := range d.Get("members").([]interface{}) { - configSet = append(configSet, setPrefix+"members "+v.(string)) - } - if d.Get("invert_match").(bool) { - configSet = append(configSet, setPrefix+"invert-match") - } - - return junSess.ConfigSet(configSet) -} - -func readPolicyoptionsCommunity(name string, junSess *junos.Session, -) (confRead communityOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "policy-options community " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - for _, item := range strings.Split(showConfig, "\n") { - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - itemTrim := strings.TrimPrefix(item, junos.SetLS) - switch { - case balt.CutPrefixInString(&itemTrim, "members "): - confRead.members = append(confRead.members, itemTrim) - case itemTrim == "invert-match": - confRead.invertMatch = true - } - } - } - - return confRead, nil -} - -func delPolicyoptionsCommunity(community string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete policy-options community "+community) - - return junSess.ConfigSet(configSet) -} - -func fillPolicyoptionsCommunityData(d *schema.ResourceData, communityOptions communityOptions) { - if tfErr := d.Set("name", communityOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("members", communityOptions.members); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("invert_match", communityOptions.invertMatch); tfErr != nil { - panic(tfErr) - } -} From 0566a5a4ff3733ae9a9e4539dd3bf179b74916da Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Wed, 24 May 2023 13:46:40 +0200 Subject: [PATCH 10/41] r/policyoptions_community: add dynamic_db argument --- .changes/policyoptions-with-fwk.md | 1 + docs/resources/policyoptions_community.md | 8 ++- .../resource_policyoptions_community.go | 71 ++++++++++++++++--- .../resource_policyoptions_test.go | 9 ++- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index edd56208..db06053f 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -10,6 +10,7 @@ ENHANCEMENTS: * **resource/junos_policyoptions_community**: * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional boolean attributes doesn't accept value *false* + * add `dynamic_db` argument (`members` is now optional but one of `dynamic_db` or `members` must be specified) BUG FIXES: diff --git a/docs/resources/policyoptions_community.md b/docs/resources/policyoptions_community.md index 12810821..c1bf3809 100644 --- a/docs/resources/policyoptions_community.md +++ b/docs/resources/policyoptions_community.md @@ -18,14 +18,18 @@ resource "junos_policyoptions_community" "community_demo" { ## Argument Reference +-> **Note:** One of `dynamic_db` or `members` arguments is required. + The following arguments are supported: - **name** (Required, String, Forces new resource) Name to identify BGP community. -- **members** (Required, List of String) - Community members. +- **dynamic_db** (Optional, Boolean) + Object may exist in dynamic database. - **invert_match** (Optional, Boolean) Invert the result of the community expression matching. +- **members** (Optional, List of String) + Community members. ## Attributes Reference diff --git a/internal/providerfwk/resource_policyoptions_community.go b/internal/providerfwk/resource_policyoptions_community.go index c08a7a40..f04e8ab2 100644 --- a/internal/providerfwk/resource_policyoptions_community.go +++ b/internal/providerfwk/resource_policyoptions_community.go @@ -23,9 +23,10 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ resource.Resource = &policyoptionsCommunity{} - _ resource.ResourceWithConfigure = &policyoptionsCommunity{} - _ resource.ResourceWithImportState = &policyoptionsCommunity{} + _ resource.Resource = &policyoptionsCommunity{} + _ resource.ResourceWithConfigure = &policyoptionsCommunity{} + _ resource.ResourceWithValidateConfig = &policyoptionsCommunity{} + _ resource.ResourceWithImportState = &policyoptionsCommunity{} ) type policyoptionsCommunity struct { @@ -94,9 +95,23 @@ func (rsc *policyoptionsCommunity) Schema( tfvalidator.StringDoubleQuoteExclusion(), }, }, + "dynamic_db": schema.BoolAttribute{ + Optional: true, + Description: "Object may exist in dynamic database.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "invert_match": schema.BoolAttribute{ + Optional: true, + Description: "Invert the result of the community expression matching.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, "members": schema.ListAttribute{ ElementType: types.StringType, - Required: true, + Optional: true, Description: "Community members.", Validators: []validator.List{ listvalidator.SizeAtLeast(1), @@ -106,24 +121,53 @@ func (rsc *policyoptionsCommunity) Schema( ), }, }, - "invert_match": schema.BoolAttribute{ - Optional: true, - Description: "Invert the result of the community expression matching.", - Validators: []validator.Bool{ - tfvalidator.BoolTrue(), - }, - }, }, } } type policyoptionsCommunityData struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` InvertMatch types.Bool `tfsdk:"invert_match"` ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Members []types.String `tfsdk:"members"` } +type policyoptionsCommunityConfig struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` + InvertMatch types.Bool `tfsdk:"invert_match"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Members types.List `tfsdk:"members"` +} + +func (rsc *policyoptionsCommunity) ValidateConfig( + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config policyoptionsCommunityConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.Members.IsNull() && + config.DynamicDB.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "one of members or dynamic_db must be specified", + ) + } + if !config.Members.IsNull() && + !config.DynamicDB.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "only one of members or dynamic_db must be specified", + ) + } +} + func (rsc *policyoptionsCommunity) Create( ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, ) { @@ -295,6 +339,9 @@ func (rscData *policyoptionsCommunityData) set( configSet := make([]string, 0) setPrefix := "set policy-options community \"" + rscData.Name.ValueString() + "\" " + if rscData.DynamicDB.ValueBool() { + configSet = append(configSet, setPrefix+"dynamic-db") + } for _, v := range rscData.Members { configSet = append(configSet, setPrefix+"members \""+v.ValueString()+"\"") } @@ -327,6 +374,8 @@ func (rscData *policyoptionsCommunityData) read( } itemTrim := strings.TrimPrefix(item, junos.SetLS) switch { + case itemTrim == "dynamic-db": + rscData.DynamicDB = types.BoolValue(true) case balt.CutPrefixInString(&itemTrim, "members "): rscData.Members = append(rscData.Members, types.StringValue(strings.Trim(itemTrim, "\""))) diff --git a/internal/providerfwk/resource_policyoptions_test.go b/internal/providerfwk/resource_policyoptions_test.go index b72d79ce..c801bf41 100644 --- a/internal/providerfwk/resource_policyoptions_test.go +++ b/internal/providerfwk/resource_policyoptions_test.go @@ -763,8 +763,13 @@ resource "junos_policyoptions_as_path_group" "testacc_policyOptions" { } } resource "junos_policyoptions_community" "testacc_policyOptions" { - name = "testacc_policyOptions" - members = ["65000:200"] + name = "testacc_policyOptions" + members = ["65000:200"] + invert_match = true +} +resource "junos_policyoptions_community" "testacc_policyOptions2" { + name = "testacc policyOptions2" + dynamic_db = true } resource "junos_policyoptions_prefix_list" "testacc_policyOptions" { name = "testacc_policyOptions" From 0a028500f18955798f89b6492eb9607e6d9c6ac1 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 25 May 2023 20:06:59 +0200 Subject: [PATCH 11/41] r/policyoptions_prefix_list: use new provider via framework --- .changes/policyoptions-with-fwk.md | 3 + docs/resources/policyoptions_prefix_list.md | 4 +- internal/providerfwk/provider.go | 1 + .../resource_policyoptions_prefix_list.go | 364 ++++++++++++++++++ .../resource_policyoptions_test.go | 4 + internal/providersdk/provider.go | 1 - .../resource_policyoptions_prefix_list.go | 341 ---------------- 7 files changed, 374 insertions(+), 344 deletions(-) create mode 100644 internal/providerfwk/resource_policyoptions_prefix_list.go delete mode 100644 internal/providersdk/resource_policyoptions_prefix_list.go diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index db06053f..eda62d34 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -11,6 +11,9 @@ ENHANCEMENTS: * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional boolean attributes doesn't accept value *false* * add `dynamic_db` argument (`members` is now optional but one of `dynamic_db` or `members` must be specified) +* **resource/junos_policyoptions_prefix_list**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + optional boolean attributes doesn't accept value *false* + optional string attributes doesn't accept *empty* value BUG FIXES: diff --git a/docs/resources/policyoptions_prefix_list.md b/docs/resources/policyoptions_prefix_list.md index 293555e1..d36f653a 100644 --- a/docs/resources/policyoptions_prefix_list.md +++ b/docs/resources/policyoptions_prefix_list.md @@ -21,13 +21,13 @@ resource "junos_policyoptions_prefix_list" "demo_plist" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of prefix list. + Prefix list name. - **apply_path** (Optional, String) Apply IP prefixes from a configuration statement. - **dynamic_db** (Optional, Boolean) Object may exist in dynamic database. - **prefix** (Optional, Set of String) - List of CIDR. + Address prefixes. ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index e8328d8b..dd6b8cca 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -209,6 +209,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newPolicyoptionsASPathResource, newPolicyoptionsASPathGroupResource, newPolicyoptionsCommunityResource, + newPolicyoptionsPrefixListResource, newRoutingInstanceResource, newSecurityResource, newSecurityAddressBookResource, diff --git a/internal/providerfwk/resource_policyoptions_prefix_list.go b/internal/providerfwk/resource_policyoptions_prefix_list.go new file mode 100644 index 00000000..a1a1a329 --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_prefix_list.go @@ -0,0 +1,364 @@ +package providerfwk + +import ( + "context" + "fmt" + "html" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "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" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &policyoptionsPrefixList{} + _ resource.ResourceWithConfigure = &policyoptionsPrefixList{} + _ resource.ResourceWithImportState = &policyoptionsPrefixList{} +) + +type policyoptionsPrefixList struct { + client *junos.Client +} + +func newPolicyoptionsPrefixListResource() resource.Resource { + return &policyoptionsPrefixList{} +} + +func (rsc *policyoptionsPrefixList) typeName() string { + return providerName + "_policyoptions_prefix_list" +} + +func (rsc *policyoptionsPrefixList) junosName() string { + return "policy-options prefix-list" +} + +func (rsc *policyoptionsPrefixList) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *policyoptionsPrefixList) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *policyoptionsPrefixList) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *policyoptionsPrefixList) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format ``.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Prefix list name.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "apply_path": schema.StringAttribute{ + Optional: true, + Description: "Apply IP prefixes from a configuration statement.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "dynamic_db": schema.BoolAttribute{ + Optional: true, + Description: "Object may exist in dynamic database.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "prefix": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Address prefixes.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringCIDRNetwork(), + ), + }, + }, + }, + } +} + +type policyoptionsPrefixListData struct { + DynamicDB types.Bool `tfsdk:"dynamic_db"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ApplyPath types.String `tfsdk:"apply_path"` + Prefix []types.String `tfsdk:"prefix"` +} + +func (rsc *policyoptionsPrefixList) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan policyoptionsPrefixListData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + listExists, err := checkPolicyoptionsPrefixListExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if listExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + listExists, err := checkPolicyoptionsPrefixListExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !listExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *policyoptionsPrefixList) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data policyoptionsPrefixListData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom1String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + }, + &data, + nil, + resp, + ) +} + +func (rsc *policyoptionsPrefixList) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state policyoptionsPrefixListData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *policyoptionsPrefixList) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state policyoptionsPrefixListData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *policyoptionsPrefixList) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data policyoptionsPrefixListData + + var _ resourceDataReadFrom1String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be )", req.ID), + ) +} + +func checkPolicyoptionsPrefixListExists( + _ context.Context, name string, junSess *junos.Session, +) ( + bool, error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options prefix-list \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *policyoptionsPrefixListData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *policyoptionsPrefixListData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *policyoptionsPrefixListData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + setPrefix := "set policy-options prefix-list \"" + rscData.Name.ValueString() + "\" " + + if v := rscData.ApplyPath.ValueString(); v != "" { + replaceSign := strings.ReplaceAll(strings.ReplaceAll(v, "<", "<"), ">", ">") + configSet = append(configSet, setPrefix+" apply-path \""+replaceSign+"\"") + } + if rscData.DynamicDB.ValueBool() { + configSet = append(configSet, setPrefix+"dynamic-db") + } + for _, v := range rscData.Prefix { + configSet = append(configSet, setPrefix+v.ValueString()) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (rscData *policyoptionsPrefixListData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options prefix-list \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case balt.CutPrefixInString(&itemTrim, "apply-path "): + rscData.ApplyPath = types.StringValue(html.UnescapeString(strings.Trim(itemTrim, "\""))) + case itemTrim == "dynamic-db": + rscData.DynamicDB = types.BoolValue(true) + case strings.Contains(itemTrim, "/"): + rscData.Prefix = append(rscData.Prefix, types.StringValue(itemTrim)) + } + } + } + + return nil +} + +func (rscData *policyoptionsPrefixListData) del( + _ context.Context, junSess *junos.Session, +) error { + configSet := []string{ + "delete policy-options prefix-list \"" + rscData.Name.ValueString() + "\"", + } + + return junSess.ConfigSet(configSet) +} diff --git a/internal/providerfwk/resource_policyoptions_test.go b/internal/providerfwk/resource_policyoptions_test.go index c801bf41..073bf83b 100644 --- a/internal/providerfwk/resource_policyoptions_test.go +++ b/internal/providerfwk/resource_policyoptions_test.go @@ -521,6 +521,10 @@ resource "junos_policyoptions_prefix_list" "testacc_policyOptions" { name = "testacc_policyOptions" prefix = ["192.0.2.0/25"] } +resource "junos_policyoptions_prefix_list" "testacc_policyOptions2" { + name = "testacc policyOptions2" + prefix = ["192.0.2.0/25", "fe80::/64"] +} resource "junos_policyoptions_policy_statement" "testacc_policyOptions" { name = "testacc_policyOptions" from { diff --git a/internal/providersdk/provider.go b/internal/providersdk/provider.go index aa42bada..acd8365a 100644 --- a/internal/providersdk/provider.go +++ b/internal/providersdk/provider.go @@ -171,7 +171,6 @@ func Provider() *schema.Provider { "junos_ospf": resourceOspf(), "junos_ospf_area": resourceOspfArea(), "junos_policyoptions_policy_statement": resourcePolicyoptionsPolicyStatement(), - "junos_policyoptions_prefix_list": resourcePolicyoptionsPrefixList(), "junos_rib_group": resourceRibGroup(), "junos_rip_group": resourceRipGroup(), "junos_rip_neighbor": resourceRipNeighbor(), diff --git a/internal/providersdk/resource_policyoptions_prefix_list.go b/internal/providersdk/resource_policyoptions_prefix_list.go deleted file mode 100644 index e635093b..00000000 --- a/internal/providersdk/resource_policyoptions_prefix_list.go +++ /dev/null @@ -1,341 +0,0 @@ -package providersdk - -import ( - "context" - "fmt" - "html" - "strings" - - "github.com/jeremmfr/terraform-provider-junos/internal/junos" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - balt "github.com/jeremmfr/go-utils/basicalter" -) - -type prefixListOptions struct { - dynamicDB bool - name string - applyPath string - prefix []string -} - -func resourcePolicyoptionsPrefixList() *schema.Resource { - return &schema.Resource{ - CreateWithoutTimeout: resourcePolicyoptionsPrefixListCreate, - ReadWithoutTimeout: resourcePolicyoptionsPrefixListRead, - UpdateWithoutTimeout: resourcePolicyoptionsPrefixListUpdate, - DeleteWithoutTimeout: resourcePolicyoptionsPrefixListDelete, - Importer: &schema.ResourceImporter{ - StateContext: resourcePolicyoptionsPrefixListImport, - }, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - ForceNew: true, - Required: true, - ValidateDiagFunc: validateNameObjectJunos([]string{}, 64, formatDefault), - }, - "apply_path": { - Type: schema.TypeString, - Optional: true, - }, - "dynamic_db": { - Type: schema.TypeBool, - Optional: true, - }, - "prefix": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validateCIDRNetworkFunc(), - }, - }, - }, - } -} - -func resourcePolicyoptionsPrefixListCreate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeCreateSetFile() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := setPolicyoptionsPrefixList(d, junSess); err != nil { - return diag.FromErr(err) - } - d.SetId(d.Get("name").(string)) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - policyoptsPrefixListExists, err := checkPolicyoptionsPrefixListExists(d.Get("name").(string), junSess) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsPrefixListExists { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, - diag.FromErr(fmt.Errorf("policy-options prefix-list %v already exists", d.Get("name").(string)))...) - } - - if err := setPolicyoptionsPrefixList(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("create resource junos_policyoptions_prefix_list") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - policyoptsPrefixListExists, err = checkPolicyoptionsPrefixListExists(d.Get("name").(string), junSess) - if err != nil { - return append(diagWarns, diag.FromErr(err)...) - } - if policyoptsPrefixListExists { - d.SetId(d.Get("name").(string)) - } else { - return append(diagWarns, diag.FromErr(fmt.Errorf("policy-options prefix-list %v not exists after commit "+ - "=> check your config", d.Get("name").(string)))...) - } - - return append(diagWarns, resourcePolicyoptionsPrefixListReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsPrefixListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - - return resourcePolicyoptionsPrefixListReadWJunSess(d, junSess) -} - -func resourcePolicyoptionsPrefixListReadWJunSess(d *schema.ResourceData, junSess *junos.Session, -) diag.Diagnostics { - junos.MutexLock() - prefixListOptions, err := readPolicyoptionsPrefixList(d.Get("name").(string), junSess) - junos.MutexUnlock() - if err != nil { - return diag.FromErr(err) - } - if prefixListOptions.name == "" { - d.SetId("") - } else { - fillPolicyoptionsPrefixListData(d, prefixListOptions) - } - - return nil -} - -func resourcePolicyoptionsPrefixListUpdate(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - d.Partial(true) - clt := m.(*junos.Client) - if clt.FakeUpdateAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsPrefixList(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - if err := setPolicyoptionsPrefixList(d, junSess); err != nil { - return diag.FromErr(err) - } - d.Partial(false) - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsPrefixList(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - if err := setPolicyoptionsPrefixList(d, junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("update resource junos_policyoptions_prefix_list") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - d.Partial(false) - - return append(diagWarns, resourcePolicyoptionsPrefixListReadWJunSess(d, junSess)...) -} - -func resourcePolicyoptionsPrefixListDelete(ctx context.Context, d *schema.ResourceData, m interface{}, -) diag.Diagnostics { - clt := m.(*junos.Client) - if clt.FakeDeleteAlso() { - junSess := clt.NewSessionWithoutNetconf(ctx) - if err := delPolicyoptionsPrefixList(d.Get("name").(string), junSess); err != nil { - return diag.FromErr(err) - } - - return nil - } - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return diag.FromErr(err) - } - defer junSess.Close() - if err := junSess.ConfigLock(ctx); err != nil { - return diag.FromErr(err) - } - var diagWarns diag.Diagnostics - if err := delPolicyoptionsPrefixList(d.Get("name").(string), junSess); err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - warns, err := junSess.CommitConf("delete resource junos_policyoptions_prefix_list") - appendDiagWarns(&diagWarns, warns) - if err != nil { - appendDiagWarns(&diagWarns, junSess.ConfigClear()) - - return append(diagWarns, diag.FromErr(err)...) - } - - return diagWarns -} - -func resourcePolicyoptionsPrefixListImport(ctx context.Context, d *schema.ResourceData, m interface{}, -) ([]*schema.ResourceData, error) { - clt := m.(*junos.Client) - junSess, err := clt.StartNewSession(ctx) - if err != nil { - return nil, err - } - defer junSess.Close() - result := make([]*schema.ResourceData, 1) - - policyoptsPrefixListExists, err := checkPolicyoptionsPrefixListExists(d.Id(), junSess) - if err != nil { - return nil, err - } - if !policyoptsPrefixListExists { - return nil, fmt.Errorf("don't find policy-options prefix-list with id '%v' (id must be )", d.Id()) - } - prefixListOptions, err := readPolicyoptionsPrefixList(d.Id(), junSess) - if err != nil { - return nil, err - } - fillPolicyoptionsPrefixListData(d, prefixListOptions) - - result[0] = d - - return result, nil -} - -func checkPolicyoptionsPrefixListExists(name string, junSess *junos.Session) (bool, error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + "policy-options prefix-list " + name + junos.PipeDisplaySet) - if err != nil { - return false, err - } - if showConfig == junos.EmptyW { - return false, nil - } - - return true, nil -} - -func setPolicyoptionsPrefixList(d *schema.ResourceData, junSess *junos.Session) error { - configSet := make([]string, 0) - - setPrefix := "set policy-options prefix-list " + d.Get("name").(string) - configSet = append(configSet, setPrefix) - if d.Get("apply_path").(string) != "" { - replaceSign := strings.ReplaceAll(d.Get("apply_path").(string), "<", "<") - replaceSign = strings.ReplaceAll(replaceSign, ">", ">") - configSet = append(configSet, setPrefix+" apply-path \""+replaceSign+"\"") - } - if d.Get("dynamic_db").(bool) { - configSet = append(configSet, setPrefix+" dynamic-db") - } - for _, v := range sortSetOfString(d.Get("prefix").(*schema.Set).List()) { - configSet = append(configSet, setPrefix+" "+v) - } - - return junSess.ConfigSet(configSet) -} - -func readPolicyoptionsPrefixList(name string, junSess *junos.Session, -) (confRead prefixListOptions, err error) { - showConfig, err := junSess.Command(junos.CmdShowConfig + - "policy-options prefix-list " + name + junos.PipeDisplaySetRelative) - if err != nil { - return confRead, err - } - if showConfig != junos.EmptyW { - confRead.name = name - for _, item := range strings.Split(showConfig, "\n") { - itemTrim := strings.TrimPrefix(item, junos.SetLS) - if strings.Contains(item, junos.XMLStartTagConfigOut) { - continue - } - if strings.Contains(item, junos.XMLEndTagConfigOut) { - break - } - switch { - case balt.CutPrefixInString(&itemTrim, "apply-path "): - confRead.applyPath = html.UnescapeString(strings.Trim(itemTrim, "\"")) - case itemTrim == "dynamic-db": - confRead.dynamicDB = true - case strings.Contains(itemTrim, "/"): - confRead.prefix = append(confRead.prefix, itemTrim) - } - } - } - - return confRead, nil -} - -func delPolicyoptionsPrefixList(prefixList string, junSess *junos.Session) error { - configSet := make([]string, 0, 1) - configSet = append(configSet, "delete policy-options prefix-list "+prefixList) - - return junSess.ConfigSet(configSet) -} - -func fillPolicyoptionsPrefixListData(d *schema.ResourceData, prefixListOptions prefixListOptions) { - if tfErr := d.Set("name", prefixListOptions.name); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("apply_path", prefixListOptions.applyPath); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("dynamic_db", prefixListOptions.dynamicDB); tfErr != nil { - panic(tfErr) - } - if tfErr := d.Set("prefix", prefixListOptions.prefix); tfErr != nil { - panic(tfErr) - } -} From d511ddcb6bc31a840b6e0873fa3fdf205dbc7ebb Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Thu, 1 Jun 2023 09:26:30 +0200 Subject: [PATCH 12/41] r/bgp_*: add error in ValidateConfig with bfd_liveness_detection when block is set but empty --- internal/providerfwk/resource_bgp_group.go | 9 +++++++++ internal/providerfwk/resource_bgp_neighbor.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/internal/providerfwk/resource_bgp_group.go b/internal/providerfwk/resource_bgp_group.go index 383dae28..213d7880 100644 --- a/internal/providerfwk/resource_bgp_group.go +++ b/internal/providerfwk/resource_bgp_group.go @@ -1021,6 +1021,15 @@ func (rsc *bgpGroup) ValidateConfig( ) } } + if config.BfdLivenessDetection != nil { + if config.BfdLivenessDetection.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("bfd_liveness_detection").AtName("*"), + tfdiag.MissingConfigErrSummary, + "bfd_liveness_detection block is empty", + ) + } + } if config.BgpErrorTolerance != nil { if !config.BgpErrorTolerance.MalformedRouteLimit.IsNull() && !config.BgpErrorTolerance.NoMalformedRouteLimit.IsNull() { diff --git a/internal/providerfwk/resource_bgp_neighbor.go b/internal/providerfwk/resource_bgp_neighbor.go index ebf04743..e7cecd5e 100644 --- a/internal/providerfwk/resource_bgp_neighbor.go +++ b/internal/providerfwk/resource_bgp_neighbor.go @@ -1019,6 +1019,15 @@ func (rsc *bgpNeighbor) ValidateConfig( ) } } + if config.BfdLivenessDetection != nil { + if config.BfdLivenessDetection.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("bfd_liveness_detection").AtName("*"), + tfdiag.MissingConfigErrSummary, + "bfd_liveness_detection block is empty", + ) + } + } if config.BgpErrorTolerance != nil { if !config.BgpErrorTolerance.MalformedRouteLimit.IsNull() && !config.BgpErrorTolerance.NoMalformedRouteLimit.IsNull() { From 509c3c35ecfef12832ab2ff83965fc5b6512765a Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Sat, 3 Jun 2023 10:56:22 +0200 Subject: [PATCH 13/41] tests: bump golangci-lint to v1.53 --- .github/workflows/linters.yml | 2 +- .golangci.yml | 1 + internal/providerfwk/resource_security_address_book.go | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 7f49f42c..7bcc7b4a 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -19,7 +19,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.52 + version: v1.53 args: -c .golangci.yml -v markdown-lint: diff --git a/.golangci.yml b/.golangci.yml index 49c6adf8..06ff9ce5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,6 +29,7 @@ linters: - execinquery - nosnakecase - musttag + - depguard linters-settings: gci: custom-order: true diff --git a/internal/providerfwk/resource_security_address_book.go b/internal/providerfwk/resource_security_address_book.go index 5cc1bafd..8aba2bd3 100644 --- a/internal/providerfwk/resource_security_address_book.go +++ b/internal/providerfwk/resource_security_address_book.go @@ -367,14 +367,14 @@ type securityAddressBookBlockWildcardAddress struct { } type securityAddressBookBlockAddressSet struct { - Name types.String `tfsdk:"name" mapstructure:"name"` + Name types.String `tfsdk:"name"` Address []types.String `tfsdk:"address"` AddressSet []types.String `tfsdk:"address_set"` Description types.String `tfsdk:"description"` } type securityAddressBookBlockAddressSetConfig struct { - Name types.String `tfsdk:"name" mapstructure:"name"` + Name types.String `tfsdk:"name"` Address types.Set `tfsdk:"address"` AddressSet types.Set `tfsdk:"address_set"` Description types.String `tfsdk:"description"` From 1c0e963633a46a2b20a2c73e90ac71b47b34e339 Mon Sep 17 00:00:00 2001 From: Jeremy Muriel Date: Mon, 5 Jun 2023 08:46:43 +0200 Subject: [PATCH 14/41] r/policyoptions_policy_statement: use new provider via framework --- .changes/policyoptions-with-fwk.md | 5 + .../policyoptions_policy_statement.md | 89 +- internal/providerfwk/provider.go | 1 + ...resource_policyoptions_policy_statement.go | 2896 +++++++++++++++++ .../resource_policyoptions_test.go | 414 +-- ...adestate_policyoptions_policy_statement.go | 298 ++ ...ate_policyoptions_policy_statement_test.go | 266 ++ internal/providersdk/func_common.go | 11 - internal/providersdk/provider.go | 1 - ...resource_policyoptions_policy_statement.go | 1716 ---------- .../resource_policyoptions_test.go | 973 ------ 11 files changed, 3677 insertions(+), 2993 deletions(-) create mode 100644 internal/providerfwk/resource_policyoptions_policy_statement.go create mode 100644 internal/providerfwk/upgradestate_policyoptions_policy_statement.go create mode 100644 internal/providerfwk/upgradestate_policyoptions_policy_statement_test.go delete mode 100644 internal/providersdk/resource_policyoptions_policy_statement.go delete mode 100644 internal/providersdk/resource_policyoptions_test.go diff --git a/.changes/policyoptions-with-fwk.md b/.changes/policyoptions-with-fwk.md index eda62d34..71e5ee1b 100644 --- a/.changes/policyoptions-with-fwk.md +++ b/.changes/policyoptions-with-fwk.md @@ -11,6 +11,11 @@ ENHANCEMENTS: * resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional boolean attributes doesn't accept value *false* * add `dynamic_db` argument (`members` is now optional but one of `dynamic_db` or `members` must be specified) +* **resource/junos_policyoptions_policy_statement**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) + some of config errors are now sent during Plan instead of during Apply + optional boolean attributes doesn't accept value *false* + optional string attributes doesn't accept *empty* value + the resource schema has been upgraded to have one-blocks in single mode instead of list * **resource/junos_policyoptions_prefix_list**: resource now use new [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) optional boolean attributes doesn't accept value *false* optional string attributes doesn't accept *empty* value diff --git a/docs/resources/policyoptions_policy_statement.md b/docs/resources/policyoptions_policy_statement.md index 76b036c4..1932c06b 100644 --- a/docs/resources/policyoptions_policy_statement.md +++ b/docs/resources/policyoptions_policy_statement.md @@ -41,30 +41,30 @@ resource "junos_policyoptions_policy_statement" "demo_policy" { The following arguments are supported: - **name** (Required, String, Forces new resource) - The name of routing policy. + Name to identify the policy. - **add_it_to_forwarding_table_export** (Optional, Boolean) Add this policy in `routing-options forwarding-table export` list. - **from** (Optional, Block) - Declare from filter. + Conditions to match the source of a route. See [below for nested schema](#from-arguments). - **to** (Optional, Block) - Declare to filter. + Conditions to match the destination of a route. See [below for nested schema](#to-arguments). - **then** (Optional, Block) - Declare then actions. + Actions to take if 'from' and 'to' conditions match. See [below for nested schema](#then-arguments). - **term** (Optional, Block List) - For each name of term. + For each policy term. - **name** (Required, String) - Name of policy + Name of term. - **from** (Optional, Block) - Declare from filter. + Conditions to match the source of a route. See [below for nested schema](#from-arguments). - **to** (Optional, Block) - Declare to filter. + Conditions to match the destination of a route. See [below for nested schema](#to-arguments). - **then** (Optional, Block) - Declare then actions. + Actions to take if 'from' and 'to' conditions match. See [below for nested schema](#then-arguments). --- @@ -118,19 +118,17 @@ The following arguments are supported: - **evpn_tag** (Optional, Set of Number) Tag in EVPN Route (0..4294967295). - **family** (Optional, String) - IP family. + Family. - **local_preference** (Optional, Number) Local preference associated with a route. -- **routing_instance** (Optional, String) - Routing protocol instance. - **interface** (Optional, Set of String) - List of interface name + Interface name or address. - **metric** (Optional, Number) - Metric value + Metric value. - **neighbor** (Optional, Set of String) - Neighboring router + Neighboring router. - **next_hop** (Optional, Set of String) - Next-hop router + Next-hop router. - **next_hop_type_merged** (Optional, Boolean) Merged next hop. - **next_hop_weight** (Optional, Block Set) @@ -141,28 +139,30 @@ The following arguments are supported: - **weight** (Required, Weight) Weight of the gateway (1..65535). - **ospf_area** (Optional, String) - OSPF area identifier + OSPF area identifier. - **policy** (Optional, List of String) - Name of policy to evaluate + Name of policy to evaluate. - **preference** (Optional, Number) - Preference value + Preference value. - **prefix_list** (Optional, Set of String) - List of prefix-lists of routes to match. + Prefix-lists of routes to match. See resource `junos_policyoptions_prefix_list`. - **protocol** (Optional, Set of String) - Protocol from which route was learned + Protocol from which route was learned. - **route_filter** (Optional, Block List) - For each filter to declare. + For each routes to match. - **route** (Required, String) - IP address + IP address. - **option** (Required, String) Mask option. Need to be `address-mask`, `exact`, `longer`, `orlonger`, `prefix-length-range`, `through` or `upto`. - **option_value** (Optional, String) - For options that need an argument + For options that need an argument. - **route_type** (Optional, String) Route type. Need to be `external` or `internal`. +- **routing_instance** (Optional, String) + Routing protocol instance. - **srte_color** (Optional, Number) Srte color. - **state** (Optional, String) @@ -192,27 +192,27 @@ The following arguments are supported: BGP origin attribute. Need to be `egp`, `igp` or `incomplete`. - **family** (Optional, String) - IP family. + Family. - **local_preference** (Optional, Number) Local preference associated with a route. -- **routing_instance** (Optional, String) - Routing protocol instance. - **interface** (Optional, Set of String) - List of interface name + Interface name or address. - **metric** (Optional, Number) - Metric value + Metric value. - **neighbor** (Optional, Set of String) - Neighboring router + Neighboring router. - **next_hop** (Optional, Set of String) - Next-hop router + Next-hop router. - **ospf_area** (Optional, String) - OSPF area identifier + OSPF area identifier. - **policy** (Optional, List of String) - Name of policy to evaluate + Name of policy to evaluate. - **preference** (Optional, Number) - Preference value + Preference value. - **protocol** (Optional, Set of String) - Protocol from which route was learned + Protocol from which route was learned. +- **routing_instance** (Optional, String) + Routing protocol instance. --- @@ -230,7 +230,7 @@ The following arguments are supported: Action on BGP community. Need to be `add`, `delete` or `set`. - **value** (Required, String) - Value for action + Name to identify a BGP community. - **default_action** (Optional, String) Set default policy action. Need to be `accept` or `reject`. @@ -243,27 +243,28 @@ The following arguments are supported: Action on local-preference. Need to be `add`, `subtract` or `none`. - **value** (Required, String) - Value for action -- **next** (Optional, String) - Skip to next `policy` or `term`. -- **next_hop** (Optional, String) - Set the address of the next-hop router + Value for action (local-preference, constant). - **metric** (Optional, Block) Declare metric action. - **action** (Required, String) Action on metric. Need to be `add`, `subtract` or `none`. - **value** (Required, String) - Value for action + Value for action (metric, constant). +- **next** (Optional, String) + Skip to next `policy` or `term`. +- **next_hop** (Optional, String) + Set the address of the next-hop router. + Need to be a valid IP or one of `discard`, `next-table`, `peer-address`, `reject`, `self`. - **origin** (Optional, String) - BGP path origin + BGP path origin. - **preference** (Optional, Block) Declare preference action. - **action** (Required, String) Action on preference. Need to be `add`, `subtract` or `none`. - **value** (Required, String) - Value for action + Value for action (preference, constant). ## Attributes Reference diff --git a/internal/providerfwk/provider.go b/internal/providerfwk/provider.go index dd6b8cca..d9f4c993 100644 --- a/internal/providerfwk/provider.go +++ b/internal/providerfwk/provider.go @@ -209,6 +209,7 @@ func (p *junosProvider) Resources(_ context.Context) []func() resource.Resource newPolicyoptionsASPathResource, newPolicyoptionsASPathGroupResource, newPolicyoptionsCommunityResource, + newPolicyoptionsPolicyStatementResource, newPolicyoptionsPrefixListResource, newRoutingInstanceResource, newSecurityResource, diff --git a/internal/providerfwk/resource_policyoptions_policy_statement.go b/internal/providerfwk/resource_policyoptions_policy_statement.go new file mode 100644 index 00000000..adfcea3e --- /dev/null +++ b/internal/providerfwk/resource_policyoptions_policy_statement.go @@ -0,0 +1,2896 @@ +package providerfwk + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/jeremmfr/terraform-provider-junos/internal/junos" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdata" + "github.com/jeremmfr/terraform-provider-junos/internal/tfdiag" + "github.com/jeremmfr/terraform-provider-junos/internal/tfplanmodifier" + "github.com/jeremmfr/terraform-provider-junos/internal/tfvalidator" + "github.com/jeremmfr/terraform-provider-junos/internal/utils" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "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" + balt "github.com/jeremmfr/go-utils/basicalter" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &policyoptionsPolicyStatement{} + _ resource.ResourceWithConfigure = &policyoptionsPolicyStatement{} + _ resource.ResourceWithValidateConfig = &policyoptionsPolicyStatement{} + _ resource.ResourceWithImportState = &policyoptionsPolicyStatement{} + _ resource.ResourceWithUpgradeState = &policyoptionsPolicyStatement{} +) + +type policyoptionsPolicyStatement struct { + client *junos.Client +} + +func newPolicyoptionsPolicyStatementResource() resource.Resource { + return &policyoptionsPolicyStatement{} +} + +func (rsc *policyoptionsPolicyStatement) typeName() string { + return providerName + "_policyoptions_policy_statement" +} + +func (rsc *policyoptionsPolicyStatement) junosName() string { + return "policy-options policy-statement" +} + +func (rsc *policyoptionsPolicyStatement) junosClient() *junos.Client { + return rsc.client +} + +func (rsc *policyoptionsPolicyStatement) Metadata( + _ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse, +) { + resp.TypeName = rsc.typeName() +} + +func (rsc *policyoptionsPolicyStatement) Configure( + ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, +) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*junos.Client) + if !ok { + unexpectedResourceConfigureType(ctx, req, resp) + + return + } + rsc.client = client +} + +func (rsc *policyoptionsPolicyStatement) Schema( + _ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse, +) { + resp.Schema = schema.Schema{ + Version: 1, + Description: "Provides a " + rsc.junosName() + ".", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "An identifier for the resource with format ``.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Name to identify the policy.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "add_it_to_forwarding_table_export": schema.BoolAttribute{ + Optional: true, + Description: "Add this policy in `routing-options forwarding-table export` list.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.SingleNestedBlock{ + Description: "Conditions to match the source of a route.", + Attributes: rsc.schemaFromAttributes(), + Blocks: rsc.schemaFromBlocks(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "to": schema.SingleNestedBlock{ + Description: "Conditions to match the destination of a route.", + Attributes: rsc.schemaToAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "then": schema.SingleNestedBlock{ + Description: "Actions to take if 'from' and 'to' conditions match.", + Attributes: rsc.schemaThenAttributes(), + Blocks: rsc.schemaThenBlocks(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "term": schema.ListNestedBlock{ + Description: "For each policy term.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "Name of term.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "from": schema.SingleNestedBlock{ + Description: "Conditions to match the source of a route.", + Attributes: rsc.schemaFromAttributes(), + Blocks: rsc.schemaFromBlocks(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "to": schema.SingleNestedBlock{ + Description: "Conditions to match the destination of a route.", + Attributes: rsc.schemaToAttributes(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "then": schema.SingleNestedBlock{ + Description: "Actions to take if 'from' and 'to' conditions match", + Attributes: rsc.schemaThenAttributes(), + Blocks: rsc.schemaThenBlocks(), + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + }, + }, + }, + }, + } +} + +func (rsc *policyoptionsPolicyStatement) schemaFromAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "aggregate_contributor": schema.BoolAttribute{ + Optional: true, + Description: "Match more specifics of an aggregate.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "bgp_as_path": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of AS path regular expression.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_as_path_group": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of AS path group.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_community": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "BGP community.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_origin": schema.StringAttribute{ + Optional: true, + Description: "BGP origin attribute.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + "bgp_srte_discriminator": schema.Int64Attribute{ + Optional: true, + Description: "Srte discriminator.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "color": schema.Int64Attribute{ + Optional: true, + Description: "Color (preference) value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "evpn_esi": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "ESI in EVPN Route.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.RegexMatches(regexp.MustCompile(`^([\d\w]{2}:){9}[\d\w]{2}$`), + "bad format or length"), + ), + }, + }, + "evpn_mac_route": schema.StringAttribute{ + Optional: true, + Description: "EVPN Mac Route type.", + Validators: []validator.String{ + stringvalidator.OneOf("mac-ipv4", "mac-ipv6", "mac-only"), + }, + }, + "evpn_tag": schema.SetAttribute{ + ElementType: types.Int64Type, + Optional: true, + Description: "Tag in EVPN Route (0..4294967295).", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueInt64sAre( + int64validator.Between(0, 4294967295), + ), + }, + }, + "family": schema.StringAttribute{ + Optional: true, + Description: "Family.", + Validators: []validator.String{ + stringvalidator.OneOf( + "evpn", "inet", "inet-mdt", "inet-mvpn", "inet-vpn", + "inet6", "inet6-mvpn", "inet6-vpn", "iso", + ), + }, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + Description: "Local preference associated with a route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "interface": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Interface name or address.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringIPAddress(), + ), + ), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "neighbor": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Neighboring router.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringIPAddress(), + ), + }, + }, + "next_hop": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Next-hop router.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringIPAddress(), + ), + }, + }, + "next_hop_type_merged": schema.BoolAttribute{ + Optional: true, + Description: "Merged next hop.", + Validators: []validator.Bool{ + tfvalidator.BoolTrue(), + }, + }, + "ospf_area": schema.StringAttribute{ + Optional: true, + Description: "OSPF area identifier.", + Validators: []validator.String{ + tfvalidator.StringIPAddress().IPv4Only(), + }, + }, + "policy": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of policy to evaluate.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "prefix_list": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Prefix-lists of routes to match.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Protocol from which route was learned.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "route_type": schema.StringAttribute{ + Optional: true, + Description: "Route type.", + Validators: []validator.String{ + stringvalidator.OneOf("external", "internal"), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Routing protocol instance.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + "srte_color": schema.Int64Attribute{ + Optional: true, + Description: "Srte color.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "state": schema.StringAttribute{ + Optional: true, + Description: "Route state.", + Validators: []validator.String{ + stringvalidator.OneOf("active", "inactive"), + }, + }, + "tunnel_type": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Tunnel type.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.OneOf("gre", "ipip", "udp"), + ), + }, + }, + "validation_database": schema.StringAttribute{ + Optional: true, + Description: "Name to identify a validation-state.", + Validators: []validator.String{ + stringvalidator.OneOf("invalid", "unknown", "valid"), + }, + }, + } +} + +func (rsc *policyoptionsPolicyStatement) schemaFromBlocks() map[string]schema.Block { + return map[string]schema.Block{ + "bgp_as_path_calc_length": schema.SetNestedBlock{ + Description: "Number of BGP ASes excluding confederations.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "count": schema.Int64Attribute{ + Required: true, + Description: "Number of ASes (0..1024).", + Validators: []validator.Int64{ + int64validator.Between(0, 1024), + }, + }, + "match": schema.StringAttribute{ + Required: true, + Description: "Type of match: equal values, higher or equal values, lower or equal values.", + Validators: []validator.String{ + stringvalidator.OneOf("equal", "orhigher", "orlower"), + }, + }, + }, + }, + }, + "bgp_as_path_unique_count": schema.SetNestedBlock{ + Description: "Number of unique BGP ASes excluding confederations.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "count": schema.Int64Attribute{ + Required: true, + Description: "Number of ASes (0..1024).", + Validators: []validator.Int64{ + int64validator.Between(0, 1024), + }, + }, + "match": schema.StringAttribute{ + Required: true, + Description: "Type of match: equal values, higher or equal values, lower or equal values.", + Validators: []validator.String{ + stringvalidator.OneOf("equal", "orhigher", "orlower"), + }, + }, + }, + }, + }, + "bgp_community_count": schema.SetNestedBlock{ + Description: "Number of BGP communities.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "count": schema.Int64Attribute{ + Required: true, + Description: "Number of communities (0..1024).", + Validators: []validator.Int64{ + int64validator.Between(0, 1024), + }, + }, + "match": schema.StringAttribute{ + Required: true, + Description: "Type of match: equal values, higher or equal values, lower or equal values.", + Validators: []validator.String{ + stringvalidator.OneOf("equal", "orhigher", "orlower"), + }, + }, + }, + }, + }, + "next_hop_weight": schema.SetNestedBlock{ + Description: "Weight of the gateway.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "match": schema.StringAttribute{ + Required: true, + Description: "Type of match for weight.", + Validators: []validator.String{ + stringvalidator.OneOf( + "equal", "greater-than", "greater-than-equal", "less-than", "less-than-equal", + ), + }, + }, + "weight": schema.Int64Attribute{ + Required: true, + Description: "Weight of the gateway (1..65535).", + Validators: []validator.Int64{ + int64validator.Between(1, 65535), + }, + }, + }, + }, + }, + "route_filter": schema.ListNestedBlock{ + Description: "Routes to match.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "route": schema.StringAttribute{ + Required: true, + Description: "IP address.", + Validators: []validator.String{ + tfvalidator.StringCIDRNetwork(), + }, + }, + "option": schema.StringAttribute{ + Required: true, + Description: "Mask option.", + Validators: []validator.String{ + stringvalidator.OneOf( + "address-mask", "exact", "longer", "orlonger", "prefix-length-range", "through", "upto", + ), + }, + }, + "option_value": schema.StringAttribute{ + Optional: true, + Description: "For options that need an argument.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + }, + }, + }, + } +} + +func (rsc *policyoptionsPolicyStatement) schemaToAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "bgp_as_path": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of AS path regular expression.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_as_path_group": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of AS path group.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_community": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "BGP community.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "bgp_origin": schema.StringAttribute{ + Optional: true, + Description: "BGP origin attribute.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + "family": schema.StringAttribute{ + Optional: true, + Description: "Family.", + Validators: []validator.String{ + stringvalidator.OneOf( + "evpn", "inet", "inet-mdt", "inet-mvpn", "inet-vpn", + "inet6", "inet6-mvpn", "inet6-vpn", "iso", + ), + }, + }, + "local_preference": schema.Int64Attribute{ + Optional: true, + Description: "Local preference associated with a route.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "interface": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Interface name or address.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringFormat(tfvalidator.InterfaceFormat), + tfvalidator.StringIPAddress(), + ), + ), + }, + }, + "metric": schema.Int64Attribute{ + Optional: true, + Description: "Metric value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "neighbor": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Neighboring router.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringIPAddress(), + ), + }, + }, + "next_hop": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Next-hop router.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + tfvalidator.StringIPAddress(), + ), + }, + }, + "ospf_area": schema.StringAttribute{ + Optional: true, + Description: "OSPF area identifier.", + Validators: []validator.String{ + tfvalidator.StringIPAddress().IPv4Only(), + }, + }, + "policy": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Name of policy to evaluate.", + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.ValueStringsAre( + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + ), + }, + }, + "preference": schema.Int64Attribute{ + Optional: true, + Description: "Preference value.", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + "protocol": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "Protocol from which route was learned.", + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre( + stringvalidator.LengthAtLeast(1), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + ), + }, + }, + "routing_instance": schema.StringAttribute{ + Optional: true, + Description: "Routing protocol instance.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 63), + tfvalidator.StringFormat(tfvalidator.DefaultFormat), + }, + }, + } +} + +func (rsc *policyoptionsPolicyStatement) schemaThenAttributes() map[string]schema.Attribute { + return map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Optional: true, + Description: "Action `accept` or `reject`.", + Validators: []validator.String{ + stringvalidator.OneOf("accept", "reject"), + }, + }, + "as_path_expand": schema.StringAttribute{ + Optional: true, + Description: "Prepend AS numbers prior to adding local-as.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "as_path_prepend": schema.StringAttribute{ + Optional: true, + Description: "Prepend AS numbers to an AS path.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + "default_action": schema.StringAttribute{ + Optional: true, + Description: "Set default policy action.", + Validators: []validator.String{ + stringvalidator.OneOf("accept", "reject"), + }, + }, + "load_balance": schema.StringAttribute{ + Optional: true, + Description: "Type of load balancing in forwarding table.", + Validators: []validator.String{ + stringvalidator.OneOf("per-packet", "consistent-hash"), + }, + }, + "next": schema.StringAttribute{ + Optional: true, + Description: "Skip to next `policy` or `term`.", + Validators: []validator.String{ + stringvalidator.OneOf("policy", "term"), + }, + }, + "next_hop": schema.StringAttribute{ + Optional: true, + Description: "Set the address of the next-hop router.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + stringvalidator.Any( + tfvalidator.StringIPAddress(), + stringvalidator.OneOf("discard", "next-table", "peer-address", "reject", "self"), + ), + }, + }, + "origin": schema.StringAttribute{ + Optional: true, + Description: "BGP path origin.", + Validators: []validator.String{ + stringvalidator.OneOf("egp", "igp", "incomplete"), + }, + }, + } +} + +func (rsc *policyoptionsPolicyStatement) schemaThenBlocks() map[string]schema.Block { + return map[string]schema.Block{ + "community": schema.ListNestedBlock{ + Description: "For each community action.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Required: true, + Description: "Action on BGP community.", + Validators: []validator.String{ + stringvalidator.OneOf("add", "delete", "set"), + }, + }, + "value": schema.StringAttribute{ + Required: true, + Description: "Name to identify a BGP community.", + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 250), + tfvalidator.StringDoubleQuoteExclusion(), + }, + }, + }, + }, + }, + "local_preference": schema.SingleNestedBlock{ + Description: "Declare local-preference action.", + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: " Action on local-preference.", + Validators: []validator.String{ + stringvalidator.OneOf("add", "subtract", "none"), + }, + }, + "value": schema.Int64Attribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Value for action (local-preference, constant).", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "metric": schema.SingleNestedBlock{ + Description: "Declare metric action.", + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Action on metric.", + Validators: []validator.String{ + stringvalidator.OneOf("add", "subtract", "none"), + }, + }, + "value": schema.Int64Attribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Value for action (metric, constant).", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + "preference": schema.SingleNestedBlock{ + Description: "Declare preference action.", + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Action on preference.", + Validators: []validator.String{ + stringvalidator.OneOf("add", "subtract", "none"), + }, + }, + "value": schema.Int64Attribute{ + Required: false, // true when SingleNestedBlock is specified + Optional: true, + Description: "Value for action (preference, constant).", + Validators: []validator.Int64{ + int64validator.Between(0, 4294967295), + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + tfplanmodifier.BlockRemoveNull(), + }, + }, + } +} + +type policyoptionsPolicyStatementData struct { + AddItToForwardingTableExport types.Bool `tfsdk:"add_it_to_forwarding_table_export"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + From *policyoptionsPolicyStatementBlockFrom `tfsdk:"from"` + To *policyoptionsPolicyStatementBlockTo `tfsdk:"to"` + Then *policyoptionsPolicyStatementBlockThen `tfsdk:"then"` + Term []policyoptionsPolicyStatementBlockTerm `tfsdk:"term"` +} + +type policyoptionsPolicyStatementConfig struct { + AddItToForwardingTableExport types.Bool `tfsdk:"add_it_to_forwarding_table_export"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + From *policyoptionsPolicyStatementBlockFromConfig `tfsdk:"from"` + To *policyoptionsPolicyStatementBlockToConfig `tfsdk:"to"` + Then *policyoptionsPolicyStatementBlockThenConfig `tfsdk:"then"` + Term types.List `tfsdk:"term"` +} + +type policyoptionsPolicyStatementBlockTerm struct { + Name types.String `tfsdk:"name"` + From *policyoptionsPolicyStatementBlockFrom `tfsdk:"from"` + To *policyoptionsPolicyStatementBlockTo `tfsdk:"to"` + Then *policyoptionsPolicyStatementBlockThen `tfsdk:"then"` +} + +func (block *policyoptionsPolicyStatementBlockTerm) isEmpty() bool { + switch { + case block.From != nil: + return false + case block.Then != nil: + return false + case block.To != nil: + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockTermConfig struct { + Name types.String `tfsdk:"name"` + From *policyoptionsPolicyStatementBlockFromConfig `tfsdk:"from"` + To *policyoptionsPolicyStatementBlockToConfig `tfsdk:"to"` + Then *policyoptionsPolicyStatementBlockThenConfig `tfsdk:"then"` +} + +func (block *policyoptionsPolicyStatementBlockTermConfig) isEmpty() bool { + switch { + case block.From != nil: + return false + case block.Then != nil: + return false + case block.To != nil: + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockFrom struct { + AggregateContributor types.Bool `tfsdk:"aggregate_contributor"` + NextHopTypeMerged types.Bool `tfsdk:"next_hop_type_merged"` + BgpASPath []types.String `tfsdk:"bgp_as_path"` + BgpASPathGroup []types.String `tfsdk:"bgp_as_path_group"` + BgpCommunity []types.String `tfsdk:"bgp_community"` + BgpOrigin types.String `tfsdk:"bgp_origin"` + BgpSrteDiscriminator types.Int64 `tfsdk:"bgp_srte_discriminator"` + Color types.Int64 `tfsdk:"color"` + EvpnESI []types.String `tfsdk:"evpn_esi"` + EvpnMACRoute types.String `tfsdk:"evpn_mac_route"` + EvpnTag []types.Int64 `tfsdk:"evpn_tag"` + Family types.String `tfsdk:"family"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + Interface []types.String `tfsdk:"interface"` + Metric types.Int64 `tfsdk:"metric"` + Neighbor []types.String `tfsdk:"neighbor"` + NextHop []types.String `tfsdk:"next_hop"` + OspfArea types.String `tfsdk:"ospf_area"` + Policy []types.String `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` + PrefixList []types.String `tfsdk:"prefix_list"` + Protocol []types.String `tfsdk:"protocol"` + RouteType types.String `tfsdk:"route_type"` + RoutingInstance types.String `tfsdk:"routing_instance"` + SrteColor types.Int64 `tfsdk:"srte_color"` + State types.String `tfsdk:"state"` + TunnelType []types.String `tfsdk:"tunnel_type"` + ValidationDatabase types.String `tfsdk:"validation_database"` + BgpASPathCalcLength []policyoptionsPolicyStatementBlockFromBlockCountMatch `tfsdk:"bgp_as_path_calc_length"` + BgpASPathUniqueCount []policyoptionsPolicyStatementBlockFromBlockCountMatch `tfsdk:"bgp_as_path_unique_count"` + BgpCommunityCount []policyoptionsPolicyStatementBlockFromBlockCountMatch `tfsdk:"bgp_community_count"` + NextHopWeight []policyoptionsPolicyStatementBlockFromBlockMatchWeight `tfsdk:"next_hop_weight"` + RouteFilter []policyoptionsPolicyStatementBlockFromBlockRouteFilter `tfsdk:"route_filter"` +} + +func (block *policyoptionsPolicyStatementBlockFrom) isEmpty() bool { + switch { + case !block.AggregateContributor.IsNull(): + return false + case !block.NextHopTypeMerged.IsNull(): + return false + case len(block.BgpASPath) != 0: + return false + case len(block.BgpASPathGroup) != 0: + return false + case len(block.BgpCommunity) != 0: + return false + case !block.BgpOrigin.IsNull(): + return false + case !block.BgpSrteDiscriminator.IsNull(): + return false + case !block.Color.IsNull(): + return false + case len(block.EvpnESI) != 0: + return false + case !block.EvpnMACRoute.IsNull(): + return false + case len(block.EvpnTag) != 0: + return false + case !block.Family.IsNull(): + return false + case !block.LocalPreference.IsNull(): + return false + case len(block.Interface) != 0: + return false + case !block.Metric.IsNull(): + return false + case len(block.Neighbor) != 0: + return false + case len(block.NextHop) != 0: + return false + case !block.OspfArea.IsNull(): + return false + case len(block.Policy) != 0: + return false + case !block.Preference.IsNull(): + return false + case len(block.PrefixList) != 0: + return false + case len(block.Protocol) != 0: + return false + case !block.RouteType.IsNull(): + return false + case !block.RoutingInstance.IsNull(): + return false + case !block.SrteColor.IsNull(): + return false + case !block.State.IsNull(): + return false + case len(block.TunnelType) != 0: + return false + case !block.ValidationDatabase.IsNull(): + return false + case len(block.BgpASPathCalcLength) != 0: + return false + case len(block.BgpASPathUniqueCount) != 0: + return false + case len(block.BgpCommunityCount) != 0: + return false + case len(block.NextHopWeight) != 0: + return false + case len(block.RouteFilter) != 0: + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockFromConfig struct { + AggregateContributor types.Bool `tfsdk:"aggregate_contributor"` + NextHopTypeMerged types.Bool `tfsdk:"next_hop_type_merged"` + BgpASPath types.Set `tfsdk:"bgp_as_path"` + BgpASPathGroup types.Set `tfsdk:"bgp_as_path_group"` + BgpCommunity types.Set `tfsdk:"bgp_community"` + BgpOrigin types.String `tfsdk:"bgp_origin"` + BgpSrteDiscriminator types.Int64 `tfsdk:"bgp_srte_discriminator"` + Color types.Int64 `tfsdk:"color"` + EvpnESI types.Set `tfsdk:"evpn_esi"` + EvpnMACRoute types.String `tfsdk:"evpn_mac_route"` + EvpnTag types.Set `tfsdk:"evpn_tag"` + Family types.String `tfsdk:"family"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + Interface types.Set `tfsdk:"interface"` + Metric types.Int64 `tfsdk:"metric"` + Neighbor types.Set `tfsdk:"neighbor"` + NextHop types.Set `tfsdk:"next_hop"` + OspfArea types.String `tfsdk:"ospf_area"` + Policy types.List `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` + PrefixList types.Set `tfsdk:"prefix_list"` + Protocol types.Set `tfsdk:"protocol"` + RouteType types.String `tfsdk:"route_type"` + RoutingInstance types.String `tfsdk:"routing_instance"` + SrteColor types.Int64 `tfsdk:"srte_color"` + State types.String `tfsdk:"state"` + TunnelType types.Set `tfsdk:"tunnel_type"` + ValidationDatabase types.String `tfsdk:"validation_database"` + BgpASPathCalcLength types.Set `tfsdk:"bgp_as_path_calc_length"` + BgpASPathUniqueCount types.Set `tfsdk:"bgp_as_path_unique_count"` + BgpCommunityCount types.Set `tfsdk:"bgp_community_count"` + NextHopWeight types.Set `tfsdk:"next_hop_weight"` + RouteFilter types.List `tfsdk:"route_filter"` +} + +func (block *policyoptionsPolicyStatementBlockFromConfig) isEmpty() bool { + switch { + case !block.AggregateContributor.IsNull(): + return false + case !block.NextHopTypeMerged.IsNull(): + return false + case !block.BgpASPath.IsNull(): + return false + case !block.BgpASPathGroup.IsNull(): + return false + case !block.BgpCommunity.IsNull(): + return false + case !block.BgpOrigin.IsNull(): + return false + case !block.BgpSrteDiscriminator.IsNull(): + return false + case !block.Color.IsNull(): + return false + case !block.EvpnESI.IsNull(): + return false + case !block.EvpnMACRoute.IsNull(): + return false + case !block.EvpnTag.IsNull(): + return false + case !block.Family.IsNull(): + return false + case !block.LocalPreference.IsNull(): + return false + case !block.Interface.IsNull(): + return false + case !block.Metric.IsNull(): + return false + case !block.Neighbor.IsNull(): + return false + case !block.NextHop.IsNull(): + return false + case !block.OspfArea.IsNull(): + return false + case !block.Policy.IsNull(): + return false + case !block.Preference.IsNull(): + return false + case !block.PrefixList.IsNull(): + return false + case !block.Protocol.IsNull(): + return false + case !block.RouteType.IsNull(): + return false + case !block.RoutingInstance.IsNull(): + return false + case !block.SrteColor.IsNull(): + return false + case !block.State.IsNull(): + return false + case !block.TunnelType.IsNull(): + return false + case !block.ValidationDatabase.IsNull(): + return false + case !block.BgpASPathCalcLength.IsNull(): + return false + case !block.BgpASPathUniqueCount.IsNull(): + return false + case !block.BgpCommunityCount.IsNull(): + return false + case !block.NextHopWeight.IsNull(): + return false + case !block.RouteFilter.IsNull(): + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockFromBlockCountMatch struct { + Count types.Int64 `tfsdk:"count"` + Match types.String `tfsdk:"match"` +} + +type policyoptionsPolicyStatementBlockFromBlockMatchWeight struct { + Match types.String `tfsdk:"match"` + Weight types.Int64 `tfsdk:"weight"` +} + +type policyoptionsPolicyStatementBlockFromBlockRouteFilter struct { + Route types.String `tfsdk:"route"` + Option types.String `tfsdk:"option"` + OptionValue types.String `tfsdk:"option_value"` +} + +type policyoptionsPolicyStatementBlockTo struct { + BgpASPath []types.String `tfsdk:"bgp_as_path"` + BgpASPathGroup []types.String `tfsdk:"bgp_as_path_group"` + BgpCommunity []types.String `tfsdk:"bgp_community"` + BgpOrigin types.String `tfsdk:"bgp_origin"` + Family types.String `tfsdk:"family"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + Interface []types.String `tfsdk:"interface"` + Metric types.Int64 `tfsdk:"metric"` + Neighbor []types.String `tfsdk:"neighbor"` + NextHop []types.String `tfsdk:"next_hop"` + OspfArea types.String `tfsdk:"ospf_area"` + Policy []types.String `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` + Protocol []types.String `tfsdk:"protocol"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +func (block *policyoptionsPolicyStatementBlockTo) isEmpty() bool { + switch { + case len(block.BgpASPath) != 0: + return false + case len(block.BgpASPathGroup) != 0: + return false + case len(block.BgpCommunity) != 0: + return false + case !block.BgpOrigin.IsNull(): + return false + case !block.Family.IsNull(): + return false + case !block.LocalPreference.IsNull(): + return false + case len(block.Interface) != 0: + return false + case !block.Metric.IsNull(): + return false + case len(block.Neighbor) != 0: + return false + case len(block.NextHop) != 0: + return false + case !block.OspfArea.IsNull(): + return false + case len(block.Policy) != 0: + return false + case !block.Preference.IsNull(): + return false + case len(block.Protocol) != 0: + return false + case !block.RoutingInstance.IsNull(): + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockToConfig struct { + BgpASPath types.Set `tfsdk:"bgp_as_path"` + BgpASPathGroup types.Set `tfsdk:"bgp_as_path_group"` + BgpCommunity types.Set `tfsdk:"bgp_community"` + BgpOrigin types.String `tfsdk:"bgp_origin"` + Family types.String `tfsdk:"family"` + LocalPreference types.Int64 `tfsdk:"local_preference"` + Interface types.Set `tfsdk:"interface"` + Metric types.Int64 `tfsdk:"metric"` + Neighbor types.Set `tfsdk:"neighbor"` + NextHop types.Set `tfsdk:"next_hop"` + OspfArea types.String `tfsdk:"ospf_area"` + Policy types.List `tfsdk:"policy"` + Preference types.Int64 `tfsdk:"preference"` + Protocol types.Set `tfsdk:"protocol"` + RoutingInstance types.String `tfsdk:"routing_instance"` +} + +func (block *policyoptionsPolicyStatementBlockToConfig) isEmpty() bool { + switch { + case !block.BgpASPath.IsNull(): + return false + case !block.BgpASPathGroup.IsNull(): + return false + case !block.BgpCommunity.IsNull(): + return false + case !block.BgpOrigin.IsNull(): + return false + case !block.Family.IsNull(): + return false + case !block.LocalPreference.IsNull(): + return false + case !block.Interface.IsNull(): + return false + case !block.Metric.IsNull(): + return false + case !block.Neighbor.IsNull(): + return false + case !block.NextHop.IsNull(): + return false + case !block.OspfArea.IsNull(): + return false + case !block.Policy.IsNull(): + return false + case !block.Preference.IsNull(): + return false + case !block.Protocol.IsNull(): + return false + case !block.RoutingInstance.IsNull(): + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockThen struct { + Action types.String `tfsdk:"action"` + ASPathExpand types.String `tfsdk:"as_path_expand"` + ASPathPrepend types.String `tfsdk:"as_path_prepend"` + DefaultAction types.String `tfsdk:"default_action"` + LoadBalance types.String `tfsdk:"load_balance"` + Next types.String `tfsdk:"next"` + NextHop types.String `tfsdk:"next_hop"` + Origin types.String `tfsdk:"origin"` + Community []policyoptionsPolicyStatementBlockThenBlockActionValue `tfsdk:"community"` + LocalPreference *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"local_preference"` + Metric *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"metric"` + Preference *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"preference"` +} + +func (block *policyoptionsPolicyStatementBlockThen) isEmpty() bool { + switch { + case !block.Action.IsNull(): + return false + case !block.ASPathExpand.IsNull(): + return false + case !block.ASPathPrepend.IsNull(): + return false + case !block.DefaultAction.IsNull(): + return false + case !block.LoadBalance.IsNull(): + return false + case !block.Next.IsNull(): + return false + case !block.NextHop.IsNull(): + return false + case !block.Origin.IsNull(): + return false + case len(block.Community) != 0: + return false + case block.LocalPreference != nil: + return false + case block.Metric != nil: + return false + case block.Preference != nil: + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockThenConfig struct { + Action types.String `tfsdk:"action"` + ASPathExpand types.String `tfsdk:"as_path_expand"` + ASPathPrepend types.String `tfsdk:"as_path_prepend"` + DefaultAction types.String `tfsdk:"default_action"` + LoadBalance types.String `tfsdk:"load_balance"` + Next types.String `tfsdk:"next"` + NextHop types.String `tfsdk:"next_hop"` + Origin types.String `tfsdk:"origin"` + Community types.List `tfsdk:"community"` + LocalPreference *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"local_preference"` + Metric *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"metric"` + Preference *policyoptionsPolicyStatementBlockThenBlockActionValueInt64 `tfsdk:"preference"` +} + +func (block *policyoptionsPolicyStatementBlockThenConfig) isEmpty() bool { + switch { + case !block.Action.IsNull(): + return false + case !block.ASPathExpand.IsNull(): + return false + case !block.ASPathPrepend.IsNull(): + return false + case !block.DefaultAction.IsNull(): + return false + case !block.LoadBalance.IsNull(): + return false + case !block.Next.IsNull(): + return false + case !block.NextHop.IsNull(): + return false + case !block.Origin.IsNull(): + return false + case !block.Community.IsNull(): + return false + case block.LocalPreference != nil: + return false + case block.Metric != nil: + return false + case block.Preference != nil: + return false + default: + return true + } +} + +type policyoptionsPolicyStatementBlockThenBlockActionValue struct { + Action types.String `tfsdk:"action"` + Value types.String `tfsdk:"value"` +} + +type policyoptionsPolicyStatementBlockThenBlockActionValueInt64 struct { + Action types.String `tfsdk:"action"` + Value types.Int64 `tfsdk:"value"` +} + +func (rsc *policyoptionsPolicyStatement) ValidateConfig( //nolint:gocognit,gocyclo + ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse, +) { + var config policyoptionsPolicyStatementConfig + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.From == nil && + config.To == nil && + config.Then == nil && + config.Term.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + tfdiag.MissingConfigErrSummary, + "at least one of from, to, then or term block must be specified", + ) + } + + if config.From != nil { + if config.From.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("*"), + tfdiag.MissingConfigErrSummary, + "from block is empty", + ) + } + if !config.From.BgpASPathCalcLength.IsNull() && !config.From.BgpASPathCalcLength.IsUnknown() { + var bgpASPathCalcLength []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := config.From.BgpASPathCalcLength.ElementsAs(ctx, &bgpASPathCalcLength, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpASPathCalcLengthCount := make(map[int64]struct{}) + for _, v := range bgpASPathCalcLength { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpASPathCalcLengthCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("bgp_as_path_calc_length"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_as_path_calc_length blocks with the same count %d"+ + " in from block", count), + ) + } + bgpASPathCalcLengthCount[count] = struct{}{} + } + } + } + if !config.From.BgpASPathUniqueCount.IsNull() && !config.From.BgpASPathUniqueCount.IsUnknown() { + var bgpASPathUniqueCount []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := config.From.BgpASPathUniqueCount.ElementsAs(ctx, &bgpASPathUniqueCount, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpASPathUniqueCountCount := make(map[int64]struct{}) + for _, v := range bgpASPathUniqueCount { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpASPathUniqueCountCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("bgp_as_path_unique_count"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_as_path_unique_count blocks with the same count %d"+ + " in from block", count), + ) + } + bgpASPathUniqueCountCount[count] = struct{}{} + } + } + } + if !config.From.BgpCommunityCount.IsNull() && !config.From.BgpCommunityCount.IsUnknown() { + var bgpCommunityCount []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := config.From.BgpCommunityCount.ElementsAs(ctx, &bgpCommunityCount, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpCommunityCountCount := make(map[int64]struct{}) + for _, v := range bgpCommunityCount { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpCommunityCountCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("bgp_community_count"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_community_count blocks with the same count %d"+ + " in from block", count), + ) + } + bgpCommunityCountCount[count] = struct{}{} + } + } + } + if !config.From.NextHopWeight.IsNull() && !config.From.NextHopWeight.IsUnknown() { + var nextHopWeight []policyoptionsPolicyStatementBlockFromBlockMatchWeight + asDiags := config.From.NextHopWeight.ElementsAs(ctx, &nextHopWeight, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + nextHopWeightBlock := make(map[string]struct{}) + for _, v := range nextHopWeight { + if !v.Match.IsNull() && !v.Match.IsUnknown() && + !v.Weight.IsNull() && !v.Weight.IsUnknown() { + values := v.Match.ValueString() + " " + utils.ConvI64toa(v.Weight.ValueInt64()) + if _, ok := nextHopWeightBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("next_hop_weight"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple next_hop_weight blocks with the same argument values %q"+ + " in from block", values), + ) + } + nextHopWeightBlock[values] = struct{}{} + } + } + } + if !config.From.RouteFilter.IsNull() && !config.From.RouteFilter.IsUnknown() { + var routeFilter []policyoptionsPolicyStatementBlockFromBlockRouteFilter + asDiags := config.From.RouteFilter.ElementsAs(ctx, &routeFilter, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + routeFilterBlock := make(map[string]struct{}) + for ii, v := range routeFilter { + if !v.Route.IsNull() && !v.Route.IsUnknown() && + !v.Option.IsNull() && !v.Option.IsUnknown() { + values := v.Route.ValueString() + " " + v.Option.ValueString() + if !v.OptionValue.IsNull() { + if v.OptionValue.IsUnknown() { + continue + } + values += " " + v.OptionValue.ValueString() + } + if _, ok := routeFilterBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("from").AtName("route_filter").AtListIndex(ii).AtName("route"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple route_filter blocks with the same argument values %q"+ + " in from block", values), + ) + } + routeFilterBlock[values] = struct{}{} + } + } + } + } + + if config.To != nil { + if config.To.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("to").AtName("*"), + tfdiag.MissingConfigErrSummary, + "to block is empty", + ) + } + } + + if config.Then != nil { + if config.Then.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("*"), + tfdiag.MissingConfigErrSummary, + "then block is empty", + ) + } + if !config.Then.Community.IsNull() && !config.Then.Community.IsUnknown() { + var community []policyoptionsPolicyStatementBlockThenBlockActionValue + asDiags := config.Then.Community.ElementsAs(ctx, &community, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + communityBlock := make(map[string]struct{}) + for i, v := range community { + if !v.Action.IsNull() && !v.Action.IsUnknown() && + !v.Value.IsNull() && !v.Value.IsUnknown() { + values := v.Action.ValueString() + " " + v.Value.ValueString() + if _, ok := communityBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("community").AtListIndex(i).AtName("action"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple community blocks with the same argument values %q"+ + " in then block", values), + ) + } + communityBlock[values] = struct{}{} + } + } + } + if config.Then.LocalPreference != nil { + if config.Then.LocalPreference.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("local_preference").AtName("action"), + tfdiag.MissingConfigErrSummary, + "action must be specified in then.local_preference block", + ) + } + if config.Then.LocalPreference.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("local_preference").AtName("value"), + tfdiag.MissingConfigErrSummary, + "value must be specified in then.local_preference block", + ) + } + } + if config.Then.Metric != nil { + if config.Then.Metric.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("metric").AtName("action"), + tfdiag.MissingConfigErrSummary, + "action must be specified in then.metric block", + ) + } + if config.Then.Metric.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("metric").AtName("value"), + tfdiag.MissingConfigErrSummary, + "value must be specified in then.metric block", + ) + } + } + if config.Then.Preference != nil { + if config.Then.Preference.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("preference").AtName("action"), + tfdiag.MissingConfigErrSummary, + "action must be specified in then.preference block", + ) + } + if config.Then.Preference.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("then").AtName("preference").AtName("value"), + tfdiag.MissingConfigErrSummary, + "value must be specified in then.preference block", + ) + } + } + } + + if !config.Term.IsNull() && !config.Term.IsUnknown() { + var term []policyoptionsPolicyStatementBlockTermConfig + asDiags := config.Term.ElementsAs(ctx, &term, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + termName := make(map[string]struct{}) + for i, block := range term { + if block.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("name"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("term block %q is empty", block.Name.ValueString()), + ) + } + if !block.Name.IsUnknown() { + name := block.Name.ValueString() + if _, ok := termName[name]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("name"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple term blocks with the same name %q", name), + ) + } + termName[name] = struct{}{} + } + if block.From != nil { + if block.From.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("*"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("from block is empty in term block %q", block.Name.ValueString()), + ) + } + if !block.From.BgpASPathCalcLength.IsNull() && !block.From.BgpASPathCalcLength.IsUnknown() { + var bgpASPathCalcLength []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := block.From.BgpASPathCalcLength.ElementsAs(ctx, &bgpASPathCalcLength, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpASPathCalcLengthCount := make(map[int64]struct{}) + for _, v := range bgpASPathCalcLength { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpASPathCalcLengthCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("bgp_as_path_calc_length"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_as_path_calc_length blocks with the same count %d"+ + " in from block in term block %q", count, block.Name.ValueString()), + ) + } + bgpASPathCalcLengthCount[count] = struct{}{} + } + } + } + if !block.From.BgpASPathUniqueCount.IsNull() && !block.From.BgpASPathUniqueCount.IsUnknown() { + var bgpASPathUniqueCount []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := block.From.BgpASPathUniqueCount.ElementsAs(ctx, &bgpASPathUniqueCount, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpASPathUniqueCountCount := make(map[int64]struct{}) + for _, v := range bgpASPathUniqueCount { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpASPathUniqueCountCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("bgp_as_path_unique_count"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_as_path_unique_count blocks with the same count %d"+ + " in from block in term block %q", count, block.Name.ValueString()), + ) + } + bgpASPathUniqueCountCount[count] = struct{}{} + } + } + } + if !block.From.BgpCommunityCount.IsNull() && !block.From.BgpCommunityCount.IsUnknown() { + var bgpCommunityCount []policyoptionsPolicyStatementBlockFromBlockCountMatch + asDiags := block.From.BgpCommunityCount.ElementsAs(ctx, &bgpCommunityCount, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + bgpCommunityCountCount := make(map[int64]struct{}) + for _, v := range bgpCommunityCount { + if !v.Count.IsNull() && !v.Count.IsUnknown() { + count := v.Count.ValueInt64() + if _, ok := bgpCommunityCountCount[count]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("bgp_community_count"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple bgp_community_count blocks with the same count %d"+ + " in from block in term block %q", count, block.Name.ValueString()), + ) + } + bgpCommunityCountCount[count] = struct{}{} + } + } + } + if !block.From.NextHopWeight.IsNull() && !block.From.NextHopWeight.IsUnknown() { + var nextHopWeight []policyoptionsPolicyStatementBlockFromBlockMatchWeight + asDiags := block.From.NextHopWeight.ElementsAs(ctx, &nextHopWeight, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + nextHopWeightBlock := make(map[string]struct{}) + for _, v := range nextHopWeight { + if !v.Match.IsNull() && !v.Match.IsUnknown() && + !v.Weight.IsNull() && !v.Weight.IsUnknown() { + values := v.Match.ValueString() + " " + utils.ConvI64toa(v.Weight.ValueInt64()) + if _, ok := nextHopWeightBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("next_hop_weight"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple next_hop_weight blocks with the same argument values %q"+ + " in from block in term block %q", values, block.Name.ValueString()), + ) + } + nextHopWeightBlock[values] = struct{}{} + } + } + } + if !block.From.RouteFilter.IsNull() && !block.From.RouteFilter.IsUnknown() { + var routeFilter []policyoptionsPolicyStatementBlockFromBlockRouteFilter + asDiags := block.From.RouteFilter.ElementsAs(ctx, &routeFilter, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + routeFilterBlock := make(map[string]struct{}) + for ii, v := range routeFilter { + if !v.Route.IsNull() && !v.Route.IsUnknown() && + !v.Option.IsNull() && !v.Option.IsUnknown() { + values := v.Route.ValueString() + " " + v.Option.ValueString() + if !v.OptionValue.IsNull() { + if v.OptionValue.IsUnknown() { + continue + } + values += " " + v.OptionValue.ValueString() + } + if _, ok := routeFilterBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("from").AtName("route_filter").AtListIndex(ii).AtName("route"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple route_filter blocks with the same argument values %q"+ + " in from block in term block %q", values, block.Name.ValueString()), + ) + } + routeFilterBlock[values] = struct{}{} + } + } + } + } + if block.To != nil { + if block.To.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("to").AtName("*"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("to block is empty in term block %q", block.Name.ValueString()), + ) + } + } + if block.Then != nil { + if block.Then.isEmpty() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("*"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("then block is empty in term block %q", block.Name.ValueString()), + ) + } + if !block.Then.Community.IsNull() && !block.Then.Community.IsUnknown() { + var community []policyoptionsPolicyStatementBlockThenBlockActionValue + asDiags := block.Then.Community.ElementsAs(ctx, &community, false) + if asDiags.HasError() { + resp.Diagnostics.Append(asDiags...) + + return + } + + communityBlock := make(map[string]struct{}) + for ii, v := range community { + if !v.Action.IsNull() && !v.Action.IsUnknown() && + !v.Value.IsNull() && !v.Value.IsUnknown() { + values := v.Action.ValueString() + " " + v.Value.ValueString() + if _, ok := communityBlock[values]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("community").AtListIndex(ii).AtName("action"), + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf("multiple community blocks with the same argument values %q"+ + " in then block in term block %q", values, block.Name.ValueString()), + ) + } + communityBlock[values] = struct{}{} + } + } + } + if block.Then.LocalPreference != nil { + if block.Then.LocalPreference.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("local_preference").AtName("action"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("action must be specified in then.local_preference block"+ + " in term block %q", block.Name.ValueString()), + ) + } + if block.Then.LocalPreference.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("local_preference").AtName("value"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("value must be specified in then.local_preference block"+ + " in term block %q", block.Name.ValueString()), + ) + } + } + if block.Then.Metric != nil { + if block.Then.Metric.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("metric").AtName("action"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("action must be specified in then.metric block"+ + " in term block %q", block.Name.ValueString()), + ) + } + if block.Then.Metric.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("metric").AtName("value"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("value must be specified in then.metric block"+ + " in term block %q", block.Name.ValueString()), + ) + } + } + if block.Then.Preference != nil { + if block.Then.Preference.Action.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("preference").AtName("action"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("action must be specified in then.preference block"+ + " in term block %q", block.Name.ValueString()), + ) + } + if block.Then.Preference.Value.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("term").AtListIndex(i).AtName("then").AtName("preference").AtName("value"), + tfdiag.MissingConfigErrSummary, + fmt.Sprintf("value must be specified in then.preference block"+ + " in term block %q", block.Name.ValueString()), + ) + } + } + } + } + } +} + +func (rsc *policyoptionsPolicyStatement) Create( + ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, +) { + var plan policyoptionsPolicyStatementData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + if plan.Name.ValueString() == "" { + resp.Diagnostics.AddAttributeError( + path.Root("name"), + "Empty Name", + "could not create "+rsc.junosName()+" with empty name", + ) + + return + } + + defaultResourceCreate( + ctx, + rsc, + func(fnCtx context.Context, junSess *junos.Session) bool { + policyStatementExists, err := checkPolicyoptionsPolicyStatementExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PreCheckErrSummary, err.Error()) + + return false + } + if policyStatementExists { + resp.Diagnostics.AddError( + tfdiag.DuplicateConfigErrSummary, + fmt.Sprintf(rsc.junosName()+" %q already exists", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + func(fnCtx context.Context, junSess *junos.Session) bool { + policyStatementExists, err := checkPolicyoptionsPolicyStatementExists(fnCtx, plan.Name.ValueString(), junSess) + if err != nil { + resp.Diagnostics.AddError(tfdiag.PostCheckErrSummary, err.Error()) + + return false + } + if !policyStatementExists { + resp.Diagnostics.AddError( + tfdiag.NotFoundErrSummary, + fmt.Sprintf(rsc.junosName()+" %q does not exists after commit "+ + "=> check your config", plan.Name.ValueString()), + ) + + return false + } + + return true + }, + &plan, + resp, + ) +} + +func (rsc *policyoptionsPolicyStatement) Read( + ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, +) { + var state, data policyoptionsPolicyStatementData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var _ resourceDataReadFrom1String = &data + defaultResourceRead( + ctx, + rsc, + []string{ + state.Name.ValueString(), + }, + &data, + func() { + if !state.AddItToForwardingTableExport.ValueBool() { + data.AddItToForwardingTableExport = types.BoolNull() + } + }, + resp, + ) +} + +func (rsc *policyoptionsPolicyStatement) Update( + ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, +) { + var plan, state policyoptionsPolicyStatementData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceUpdate( + ctx, + rsc, + &state, + &plan, + resp, + ) +} + +func (rsc *policyoptionsPolicyStatement) Delete( + ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, +) { + var state policyoptionsPolicyStatementData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + defaultResourceDelete( + ctx, + rsc, + &state, + resp, + ) +} + +func (rsc *policyoptionsPolicyStatement) ImportState( + ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, +) { + var data policyoptionsPolicyStatementData + + var _ resourceDataReadFrom1String = &data + defaultResourceImportState( + ctx, + rsc, + &data, + req, + resp, + fmt.Sprintf("don't find "+rsc.junosName()+" with id %q "+ + "(id must be )", req.ID), + ) + + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("add_it_to_forwarding_table_export"), types.BoolNull())...) +} + +func checkPolicyoptionsPolicyStatementExists( + _ context.Context, name string, junSess *junos.Session, +) ( + _ bool, err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options policy-statement \"" + name + "\"" + junos.PipeDisplaySet) + if err != nil { + return false, err + } + if showConfig == junos.EmptyW { + return false, nil + } + + return true, nil +} + +func (rscData *policyoptionsPolicyStatementData) fillID() { + rscData.ID = types.StringValue(rscData.Name.ValueString()) +} + +func (rscData *policyoptionsPolicyStatementData) nullID() bool { + return rscData.ID.IsNull() +} + +func (rscData *policyoptionsPolicyStatementData) set( + _ context.Context, junSess *junos.Session, +) ( + path.Path, error, +) { + configSet := make([]string, 0) + + setPrefix := "set policy-options policy-statement \"" + rscData.Name.ValueString() + "\" " + if rscData.From != nil { + if rscData.From.isEmpty() { + return path.Root("from").AtName("*"), + fmt.Errorf("from block is empty") + } + blockSet, pathErr, err := rscData.From.configSet(setPrefix, path.Root("from")) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + if rscData.To != nil { + if rscData.To.isEmpty() { + return path.Root("to").AtName("*"), + fmt.Errorf("to block is empty") + } + configSet = append(configSet, rscData.To.configSet(setPrefix)...) + } + if rscData.Then != nil { + if rscData.Then.isEmpty() { + return path.Root("then").AtName("*"), + fmt.Errorf("then block is empty") + } + blockSet, pathErr, err := rscData.Then.configSet(setPrefix, path.Root("then")) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + termName := make(map[string]struct{}) + for i, block := range rscData.Term { + name := block.Name.ValueString() + if _, ok := termName[name]; ok { + return path.Root("term").AtListIndex(i).AtName("name"), + fmt.Errorf("multiple term blocks with the same name %q", name) + } + termName[name] = struct{}{} + if block.isEmpty() { + return path.Root("term").AtListIndex(i).AtName("name"), + fmt.Errorf("term block %q is empty", name) + } + setPrefixTerm := setPrefix + "term \"" + name + "\" " + if block.From != nil { + if rscData.From.isEmpty() { + return path.Root("term").AtListIndex(i).AtName("from").AtName("*"), + fmt.Errorf("from block is empty in term block %q", name) + } + blockSet, pathErr, err := block.From.configSet(setPrefixTerm, path.Root("term").AtListIndex(i).AtName("from")) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + if block.To != nil { + if rscData.To.isEmpty() { + return path.Root("term").AtListIndex(i).AtName("to").AtName("*"), + fmt.Errorf("to block is empty in term block %q", name) + } + configSet = append(configSet, block.To.configSet(setPrefixTerm)...) + } + if block.Then != nil { + if rscData.Then.isEmpty() { + return path.Root("term").AtListIndex(i).AtName("then").AtName("*"), + fmt.Errorf("then block is empty in term block %q", name) + } + blockSet, pathErr, err := block.Then.configSet(setPrefixTerm, path.Root("term").AtListIndex(i).AtName("then")) + if err != nil { + return pathErr, err + } + configSet = append(configSet, blockSet...) + } + } + if rscData.AddItToForwardingTableExport.ValueBool() { + configSet = append(configSet, + "set routing-options forwarding-table export \""+rscData.Name.ValueString()+"\"", + ) + } + + return path.Empty(), junSess.ConfigSet(configSet) +} + +func (block *policyoptionsPolicyStatementBlockFrom) configSet( + setPrefix string, pathRoot path.Path, +) ( + []string, // configSet + path.Path, // pathErr + error, // error +) { + configSet := make([]string, 0) + setPrefix += "from " + + if block.AggregateContributor.ValueBool() { + configSet = append(configSet, setPrefix+"aggregate-contributor") + } + for _, v := range block.BgpASPath { + configSet = append(configSet, setPrefix+"as-path \""+v.ValueString()+"\"") + } + bgpASPathCalcLengthCount := make(map[int64]struct{}) + for _, v := range block.BgpASPathCalcLength { + count := v.Count.ValueInt64() + if _, ok := bgpASPathCalcLengthCount[count]; ok { + return configSet, + pathRoot.AtName("bgp_as_path_calc_length"), + fmt.Errorf("multiple bgp_as_path_calc_length blocks with the same count %d in from block", count) + } + bgpASPathCalcLengthCount[count] = struct{}{} + configSet = append(configSet, + setPrefix+"as-path-calc-length "+utils.ConvI64toa(count)+" "+v.Match.ValueString()) + } + for _, v := range block.BgpASPathGroup { + configSet = append(configSet, setPrefix+"as-path-group \""+v.ValueString()+"\"") + } + bgpASPathUniqueCountCount := make(map[int64]struct{}) + for _, v := range block.BgpASPathUniqueCount { + count := v.Count.ValueInt64() + if _, ok := bgpASPathUniqueCountCount[count]; ok { + return configSet, + pathRoot.AtName("bgp_as_path_unique_count"), + fmt.Errorf("multiple bgp_as_path_unique_count blocks with the same count %d in from block", count) + } + bgpASPathUniqueCountCount[count] = struct{}{} + configSet = append(configSet, + setPrefix+"as-path-unique-count "+utils.ConvI64toa(count)+" "+v.Match.ValueString()) + } + for _, v := range block.BgpCommunity { + configSet = append(configSet, setPrefix+"community \""+v.ValueString()+"\"") + } + bgpCommunityCountCount := make(map[int64]struct{}) + for _, v := range block.BgpCommunityCount { + count := v.Count.ValueInt64() + if _, ok := bgpCommunityCountCount[count]; ok { + return configSet, + pathRoot.AtName("bgp_community_count"), + fmt.Errorf("multiple bgp_community_count blocks with the same count %d in from block", count) + } + bgpCommunityCountCount[count] = struct{}{} + configSet = append(configSet, + setPrefix+"community-count "+utils.ConvI64toa(count)+" "+v.Match.ValueString()) + } + if v := block.BgpOrigin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"origin "+v) + } + if !block.BgpSrteDiscriminator.IsNull() { + configSet = append(configSet, setPrefix+"bgp-srte-discriminator "+ + utils.ConvI64toa(block.BgpSrteDiscriminator.ValueInt64())) + } + if !block.Color.IsNull() { + configSet = append(configSet, setPrefix+"color "+ + utils.ConvI64toa(block.Color.ValueInt64())) + } + for _, v := range block.EvpnESI { + configSet = append(configSet, setPrefix+"evpn-esi "+v.ValueString()) + } + if v := block.EvpnMACRoute.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"evpn-mac-route "+v) + } + for _, v := range block.EvpnTag { + configSet = append(configSet, setPrefix+"evpn-tag "+ + utils.ConvI64toa(v.ValueInt64())) + } + if v := block.Family.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"family "+v) + } + if !block.LocalPreference.IsNull() { + configSet = append(configSet, setPrefix+"local-preference "+ + utils.ConvI64toa(block.LocalPreference.ValueInt64())) + } + for _, v := range block.Interface { + configSet = append(configSet, setPrefix+"interface "+v.ValueString()) + } + if !block.Metric.IsNull() { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(block.Metric.ValueInt64())) + } + for _, v := range block.Neighbor { + configSet = append(configSet, setPrefix+"neighbor "+v.ValueString()) + } + for _, v := range block.NextHop { + configSet = append(configSet, setPrefix+"next-hop "+v.ValueString()) + } + if block.NextHopTypeMerged.ValueBool() { + configSet = append(configSet, setPrefix+"next-hop-type merged") + } + nextHopWeightBlock := make(map[string]struct{}) + for _, v := range block.NextHopWeight { + values := v.Match.ValueString() + " " + utils.ConvI64toa(v.Weight.ValueInt64()) + if _, ok := nextHopWeightBlock[values]; ok { + return configSet, + pathRoot.AtName("next_hop_weight"), + fmt.Errorf("multiple next_hop_weight blocks with the same argument values %q in from block", values) + } + nextHopWeightBlock[values] = struct{}{} + configSet = append(configSet, + setPrefix+"nexthop-weight "+v.Match.ValueString()+" "+utils.ConvI64toa(v.Weight.ValueInt64())) + } + if v := block.OspfArea.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"area "+v) + } + for _, v := range block.Policy { + configSet = append(configSet, setPrefix+"policy \""+v.ValueString()+"\"") + } + if !block.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(block.Preference.ValueInt64())) + } + for _, v := range block.PrefixList { + configSet = append(configSet, setPrefix+"prefix-list \""+v.ValueString()+"\"") + } + for _, v := range block.Protocol { + configSet = append(configSet, setPrefix+"protocol "+v.ValueString()) + } + routeFilterBlock := make(map[string]struct{}) + for i, v := range block.RouteFilter { + values := v.Route.ValueString() + " " + v.Option.ValueString() + " " + v.OptionValue.ValueString() + if _, ok := routeFilterBlock[values]; ok { + return configSet, + pathRoot.AtName("route_filter").AtListIndex(i).AtName("route"), + fmt.Errorf("multiple route_filter blocks with the same argument values %q in from block", values) + } + routeFilterBlock[values] = struct{}{} + setRoutFilter := setPrefix + "route-filter " + + v.Route.ValueString() + " " + v.Option.ValueString() + if v2 := v.OptionValue.ValueString(); v2 != "" { + setRoutFilter += " " + v2 + } + configSet = append(configSet, setRoutFilter) + } + if v := block.RouteType.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"route-type "+v) + } + if v := block.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"instance "+v) + } + if !block.SrteColor.IsNull() { + configSet = append(configSet, setPrefix+"srte-color "+ + utils.ConvI64toa(block.SrteColor.ValueInt64())) + } + if v := block.State.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"state "+v) + } + for _, v := range block.TunnelType { + configSet = append(configSet, setPrefix+"tunnel-type "+v.ValueString()) + } + if v := block.ValidationDatabase.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"validation-database "+v) + } + + return configSet, path.Empty(), nil +} + +func (block *policyoptionsPolicyStatementBlockTo) configSet(setPrefix string) []string { + configSet := make([]string, 0) + setPrefix += "to " + + for _, v := range block.BgpASPath { + configSet = append(configSet, setPrefix+"as-path \""+v.ValueString()+"\"") + } + for _, v := range block.BgpASPathGroup { + configSet = append(configSet, setPrefix+"as-path-group \""+v.ValueString()+"\"") + } + for _, v := range block.BgpCommunity { + configSet = append(configSet, setPrefix+"community \""+v.ValueString()+"\"") + } + if v := block.BgpOrigin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"origin "+v) + } + if v := block.Family.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"family "+v) + } + if !block.LocalPreference.IsNull() { + configSet = append(configSet, setPrefix+"local-preference "+ + utils.ConvI64toa(block.LocalPreference.ValueInt64())) + } + for _, v := range block.Interface { + configSet = append(configSet, setPrefix+"interface "+v.ValueString()) + } + if !block.Metric.IsNull() { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(block.Metric.ValueInt64())) + } + for _, v := range block.Neighbor { + configSet = append(configSet, setPrefix+"neighbor "+v.ValueString()) + } + for _, v := range block.NextHop { + configSet = append(configSet, setPrefix+"next-hop "+v.ValueString()) + } + if v := block.OspfArea.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"area "+v) + } + for _, v := range block.Policy { + configSet = append(configSet, setPrefix+"policy \""+v.ValueString()+"\"") + } + if !block.Preference.IsNull() { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(block.Preference.ValueInt64())) + } + for _, v := range block.Protocol { + configSet = append(configSet, setPrefix+"protocol "+v.ValueString()) + } + if v := block.RoutingInstance.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"instance "+v) + } + + return configSet +} + +func (block *policyoptionsPolicyStatementBlockThen) configSet( + setPrefix string, pathRoot path.Path, +) ( + []string, // configSet + path.Path, // pathErr + error, // error +) { + configSet := make([]string, 0) + setPrefix += "then " + + if v := block.Action.ValueString(); v != "" { + configSet = append(configSet, setPrefix+v) + } + if v := block.ASPathExpand.ValueString(); v != "" { + if strings.HasPrefix(v, "last-as") { + configSet = append(configSet, setPrefix+"as-path-expand "+v) + } else { + configSet = append(configSet, setPrefix+"as-path-expand \""+v+"\"") + } + } + if v := block.ASPathPrepend.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"as-path-prepend \""+v+"\"") + } + communityBlock := make(map[string]struct{}) + for i, v := range block.Community { + values := v.Action.ValueString() + " " + v.Value.ValueString() + if _, ok := communityBlock[values]; ok { + return configSet, + pathRoot.AtName("community").AtListIndex(i), + fmt.Errorf("multiple community blocks with the same argument values %q in then block", values) + } + communityBlock[values] = struct{}{} + configSet = append(configSet, setPrefix+ + "community "+v.Action.ValueString()+" \""+v.Value.ValueString()+"\"") + } + if v := block.DefaultAction.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"default-action "+v) + } + if v := block.LoadBalance.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"load-balance "+v) + } + if block.LocalPreference != nil { + if block.LocalPreference.Action.ValueString() == "none" { + configSet = append(configSet, setPrefix+"local-preference "+ + utils.ConvI64toa(block.LocalPreference.Value.ValueInt64())) + } else { + configSet = append(configSet, setPrefix+"local-preference "+ + block.LocalPreference.Action.ValueString()+" "+ + utils.ConvI64toa(block.LocalPreference.Value.ValueInt64())) + } + } + if block.Metric != nil { + if block.Metric.Action.ValueString() == "none" { + configSet = append(configSet, setPrefix+"metric "+ + utils.ConvI64toa(block.Metric.Value.ValueInt64())) + } else { + configSet = append(configSet, setPrefix+"metric "+ + block.Metric.Action.ValueString()+" "+ + utils.ConvI64toa(block.Metric.Value.ValueInt64())) + } + } + if v := block.Next.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"next "+v) + } + if v := block.NextHop.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"next-hop "+v) + } + if v := block.Origin.ValueString(); v != "" { + configSet = append(configSet, setPrefix+"origin "+v) + } + if block.Preference != nil { + if block.Preference.Action.ValueString() == "none" { + configSet = append(configSet, setPrefix+"preference "+ + utils.ConvI64toa(block.Preference.Value.ValueInt64())) + } else { + configSet = append(configSet, setPrefix+"preference "+ + block.Preference.Action.ValueString()+" "+ + utils.ConvI64toa(block.Preference.Value.ValueInt64())) + } + } + + return configSet, path.Empty(), nil +} + +func (rscData *policyoptionsPolicyStatementData) read( + _ context.Context, name string, junSess *junos.Session, +) ( + err error, +) { + showConfig, err := junSess.Command(junos.CmdShowConfig + + "policy-options policy-statement \"" + name + "\"" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfig != junos.EmptyW { + rscData.Name = types.StringValue(name) + rscData.fillID() + for _, item := range strings.Split(showConfig, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + switch { + case balt.CutPrefixInString(&itemTrim, "term "): + name := tfdata.FirstElementOfJunosLine(itemTrim) + var term policyoptionsPolicyStatementBlockTerm + rscData.Term, term = tfdata.ExtractBlockWithTFTypesString( + rscData.Term, "Name", strings.Trim(name, "\"")) + term.Name = types.StringValue(strings.Trim(name, "\"")) + balt.CutPrefixInString(&itemTrim, name+" ") + switch { + case balt.CutPrefixInString(&itemTrim, "from "): + if term.From == nil { + term.From = &policyoptionsPolicyStatementBlockFrom{} + } + if err := term.From.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "to "): + if term.To == nil { + term.To = &policyoptionsPolicyStatementBlockTo{} + } + if err := term.To.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "then "): + if term.Then == nil { + term.Then = &policyoptionsPolicyStatementBlockThen{} + } + if err := term.Then.read(itemTrim); err != nil { + return err + } + } + rscData.Term = append(rscData.Term, term) + case balt.CutPrefixInString(&itemTrim, "from "): + if rscData.From == nil { + rscData.From = &policyoptionsPolicyStatementBlockFrom{} + } + if err := rscData.From.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "to "): + if rscData.To == nil { + rscData.To = &policyoptionsPolicyStatementBlockTo{} + } + if err := rscData.To.read(itemTrim); err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "then "): + if rscData.Then == nil { + rscData.Then = &policyoptionsPolicyStatementBlockThen{} + } + if err := rscData.Then.read(itemTrim); err != nil { + return err + } + } + } + } + + showConfigForwardingTableExport, err := junSess.Command(junos.CmdShowConfig + + "routing-options forwarding-table export" + junos.PipeDisplaySetRelative) + if err != nil { + return err + } + if showConfigForwardingTableExport != junos.EmptyW { + for _, item := range strings.Split(showConfigForwardingTableExport, "\n") { + if strings.Contains(item, junos.XMLStartTagConfigOut) { + continue + } + if strings.Contains(item, junos.XMLEndTagConfigOut) { + break + } + itemTrim := strings.TrimPrefix(item, junos.SetLS) + balt.CutSuffixInString(&itemTrim, " ") + if itemTrim == name || itemTrim == "\""+name+"\"" { + rscData.AddItToForwardingTableExport = types.BoolValue(true) + } + } + } + + return nil +} + +func (block *policyoptionsPolicyStatementBlockFrom) read(itemTrim string) (err error) { + switch { + case itemTrim == "aggregate-contributor": + block.AggregateContributor = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "as-path "): + block.BgpASPath = append(block.BgpASPath, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "as-path-calc-length "): + itemTrimFields := strings.Split(itemTrim, " ") + count, err := tfdata.ConvAtoi64Value(itemTrimFields[0]) + if err != nil { + return err + } + block.BgpASPathCalcLength = append(block.BgpASPathCalcLength, + policyoptionsPolicyStatementBlockFromBlockCountMatch{ + Count: count, + Match: types.StringValue(strings.TrimPrefix(itemTrim, itemTrimFields[0]+" ")), + }, + ) + case balt.CutPrefixInString(&itemTrim, "as-path-group "): + block.BgpASPathGroup = append(block.BgpASPathGroup, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "as-path-unique-count "): + itemTrimFields := strings.Split(itemTrim, " ") + count, err := tfdata.ConvAtoi64Value(itemTrimFields[0]) + if err != nil { + return err + } + block.BgpASPathUniqueCount = append(block.BgpASPathUniqueCount, + policyoptionsPolicyStatementBlockFromBlockCountMatch{ + Count: count, + Match: types.StringValue(strings.TrimPrefix(itemTrim, itemTrimFields[0]+" ")), + }, + ) + case balt.CutPrefixInString(&itemTrim, "community "): + block.BgpCommunity = append(block.BgpCommunity, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "community-count "): + itemTrimFields := strings.Split(itemTrim, " ") + count, err := tfdata.ConvAtoi64Value(itemTrimFields[0]) + if err != nil { + return err + } + block.BgpCommunityCount = append(block.BgpCommunityCount, + policyoptionsPolicyStatementBlockFromBlockCountMatch{ + Count: count, + Match: types.StringValue(strings.TrimPrefix(itemTrim, itemTrimFields[0]+" ")), + }, + ) + case balt.CutPrefixInString(&itemTrim, "origin "): + block.BgpOrigin = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "bgp-srte-discriminator "): + block.BgpSrteDiscriminator, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "color "): + block.Color, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "evpn-esi "): + block.EvpnESI = append(block.EvpnESI, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "evpn-mac-route "): + block.EvpnMACRoute = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "evpn-tag "): + evpnTag, err := tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + block.EvpnTag = append(block.EvpnTag, evpnTag) + case balt.CutPrefixInString(&itemTrim, "family "): + block.Family = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "local-preference "): + block.LocalPreference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "interface "): + block.Interface = append(block.Interface, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "metric "): + block.Metric, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "neighbor "): + block.Neighbor = append(block.Neighbor, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "next-hop "): + block.NextHop = append(block.NextHop, types.StringValue(itemTrim)) + case itemTrim == "next-hop-type merged": + block.NextHopTypeMerged = types.BoolValue(true) + case balt.CutPrefixInString(&itemTrim, "nexthop-weight "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { // + return fmt.Errorf(junos.CantReadValuesNotEnoughFields, "nexthop-weight", itemTrim) + } + weight, err := tfdata.ConvAtoi64Value(itemTrimFields[1]) + if err != nil { + return err + } + block.NextHopWeight = append(block.NextHopWeight, + policyoptionsPolicyStatementBlockFromBlockMatchWeight{ + Match: types.StringValue(itemTrimFields[0]), + Weight: weight, + }) + case balt.CutPrefixInString(&itemTrim, "area "): + block.OspfArea = types.StringValue(itemTrim) + case balt.CutPrefixInString(&itemTrim, "policy "): + block.Policy = append(block.Policy, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "preference "): + block.Preference, err = tfdata.ConvAtoi64Value(itemTrim) + if err != nil { + return err + } + case balt.CutPrefixInString(&itemTrim, "prefix-list "): + block.PrefixList = append(block.PrefixList, types.StringValue(strings.Trim(itemTrim, "\""))) + case balt.CutPrefixInString(&itemTrim, "protocol "): + block.Protocol = append(block.Protocol, types.StringValue(itemTrim)) + case balt.CutPrefixInString(&itemTrim, "route-filter "): + itemTrimFields := strings.Split(itemTrim, " ") + if len(itemTrimFields) < 2 { //